+
@@ -399,6 +430,7 @@
has2FA: false,
wallet: {},
walletReload: false,
+ distlists: [],
domains: [],
skus: [],
sku2FA: null,
@@ -490,6 +522,12 @@
.then(response => {
this.domains = response.data.list
})
+
+ // Fetch distribution lists
+ axios.get('/api/v4/groups?owner=' + user_id)
+ .then(response => {
+ this.distlists = response.data.list
+ })
})
.catch(this.$root.errorHandler)
},
diff --git a/src/routes/api.php b/src/routes/api.php
--- a/src/routes/api.php
+++ b/src/routes/api.php
@@ -153,6 +153,11 @@
Route::post('domains/{id}/unsuspend', 'API\V4\Admin\DomainsController@unsuspend');
Route::apiResource('entitlements', API\V4\Admin\EntitlementsController::class);
+
+ Route::apiResource('groups', API\V4\Admin\GroupsController::class);
+ Route::post('groups/{id}/suspend', 'API\V4\Admin\GroupsController@suspend');
+ Route::post('groups/{id}/unsuspend', 'API\V4\Admin\GroupsController@unsuspend');
+
Route::apiResource('packages', API\V4\Admin\PackagesController::class);
Route::apiResource('skus', API\V4\Admin\SkusController::class);
Route::apiResource('users', API\V4\Admin\UsersController::class);
diff --git a/src/tests/Browser/Admin/DistlistTest.php b/src/tests/Browser/Admin/DistlistTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Browser/Admin/DistlistTest.php
@@ -0,0 +1,128 @@
+deleteTestGroup('group-test@kolab.org');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function tearDown(): void
+ {
+ $this->deleteTestGroup('group-test@kolab.org');
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test distlist info page (unauthenticated)
+ */
+ public function testDistlistUnauth(): void
+ {
+ // Test that the page requires authentication
+ $this->browse(function (Browser $browser) {
+ $user = $this->getTestUser('john@kolab.org');
+ $group = $this->getTestGroup('group-test@kolab.org');
+ $group->assignToWallet($user->wallets->first());
+
+ $browser->visit('/distlist/' . $group->id)->on(new Home());
+ });
+ }
+
+ /**
+ * Test distribution list info page
+ */
+ public function testInfo(): void
+ {
+ Queue::fake();
+
+ $this->browse(function (Browser $browser) {
+ $user = $this->getTestUser('john@kolab.org');
+ $group = $this->getTestGroup('group-test@kolab.org');
+ $group->assignToWallet($user->wallets->first());
+ $group->members = ['test1@gmail.com', 'test2@gmail.com'];
+ $group->save();
+
+ $distlist_page = new DistlistPage($group->id);
+ $user_page = new UserPage($user->id);
+
+ // Goto the distlist page
+ $browser->visit(new Home())
+ ->submitLogon('jeroen@jeroen.jeroen', 'jeroen', true)
+ ->on(new Dashboard())
+ ->visit($user_page)
+ ->on($user_page)
+ ->click('@nav #tab-distlists')
+ ->pause(1000)
+ ->click('@user-distlists table tbody tr:first-child td a')
+ ->on($distlist_page)
+ ->assertSeeIn('@distlist-info .card-title', $group->email)
+ ->with('@distlist-info form', function (Browser $browser) use ($group) {
+ $browser->assertElementsCount('.row', 3)
+ ->assertSeeIn('.row:nth-child(1) label', 'ID (Created at)')
+ ->assertSeeIn('.row:nth-child(1) #distlistid', "{$group->id} ({$group->created_at})")
+ ->assertSeeIn('.row:nth-child(2) label', 'Status')
+ ->assertSeeIn('.row:nth-child(2) #status.text-danger', 'Not Ready')
+ ->assertSeeIn('.row:nth-child(3) label', 'Recipients')
+ ->assertSeeIn('.row:nth-child(3) #members', $group->members[0])
+ ->assertSeeIn('.row:nth-child(3) #members', $group->members[1]);
+ });
+
+ // Test invalid group identifier
+ $browser->visit('/distlist/abc')->assertErrorPage(404);
+ });
+ }
+
+ /**
+ * Test suspending/unsuspending a distribution list
+ *
+ * @depends testInfo
+ */
+ public function testSuspendAndUnsuspend(): void
+ {
+ Queue::fake();
+
+ $this->browse(function (Browser $browser) {
+ $user = $this->getTestUser('john@kolab.org');
+ $group = $this->getTestGroup('group-test@kolab.org');
+ $group->assignToWallet($user->wallets->first());
+ $group->status = Group::STATUS_ACTIVE | Group::STATUS_LDAP_READY;
+ $group->save();
+
+ $browser->visit(new DistlistPage($group->id))
+ ->assertVisible('@distlist-info #button-suspend')
+ ->assertMissing('@distlist-info #button-unsuspend')
+ ->assertSeeIn('@distlist-info #status.text-success', 'Active')
+ ->click('@distlist-info #button-suspend')
+ ->assertToast(Toast::TYPE_SUCCESS, 'Distribution list suspended successfully.')
+ ->assertSeeIn('@distlist-info #status.text-warning', 'Suspended')
+ ->assertMissing('@distlist-info #button-suspend')
+ ->click('@distlist-info #button-unsuspend')
+ ->assertToast(Toast::TYPE_SUCCESS, 'Distribution list unsuspended successfully.')
+ ->assertSeeIn('@distlist-info #status.text-success', 'Active')
+ ->assertVisible('@distlist-info #button-suspend')
+ ->assertMissing('@distlist-info #button-unsuspend');
+ });
+ }
+}
diff --git a/src/tests/Browser/Admin/UserTest.php b/src/tests/Browser/Admin/UserTest.php
--- a/src/tests/Browser/Admin/UserTest.php
+++ b/src/tests/Browser/Admin/UserTest.php
@@ -39,7 +39,7 @@
$wallet->save();
Entitlement::where('cost', '>=', 5000)->delete();
-
+ $this->deleteTestGroup('group-test@kolab.org');
$this->clearMeetEntitlements();
}
@@ -61,7 +61,7 @@
$wallet->save();
Entitlement::where('cost', '>=', 5000)->delete();
-
+ $this->deleteTestGroup('group-test@kolab.org');
$this->clearMeetEntitlements();
parent::tearDown();
@@ -116,7 +116,7 @@
// Some tabs are loaded in background, wait a second
$browser->pause(500)
- ->assertElementsCount('@nav a', 5);
+ ->assertElementsCount('@nav a', 6);
// Note: Finances tab is tested in UserFinancesTest.php
$browser->assertSeeIn('@nav #tab-finances', 'Finances');
@@ -160,6 +160,14 @@
$browser->assertElementsCount('table tbody tr', 0)
->assertSeeIn('table tfoot tr td', 'There are no users in this account.');
});
+
+ // Assert Distribution lists tab
+ $browser->assertSeeIn('@nav #tab-distlists', 'Distribution lists (0)')
+ ->click('@nav #tab-distlists')
+ ->with('@user-distlists', function (Browser $browser) {
+ $browser->assertElementsCount('table tbody tr', 0)
+ ->assertSeeIn('table tfoot tr td', 'There are no distribution lists in this account.');
+ });
});
}
@@ -178,6 +186,8 @@
$wallet->discount()->associate($discount);
$wallet->debit(2010);
$wallet->save();
+ $group = $this->getTestGroup('group-test@kolab.org');
+ $group->assignToWallet($john->wallets->first());
// Click the managed-by link on Jack's page
$browser->click('@user-info #manager a')
@@ -212,7 +222,7 @@
// Some tabs are loaded in background, wait a second
$browser->pause(500)
- ->assertElementsCount('@nav a', 5);
+ ->assertElementsCount('@nav a', 6);
// Note: Finances tab is tested in UserFinancesTest.php
$browser->assertSeeIn('@nav #tab-finances', 'Finances');
@@ -251,6 +261,16 @@
->assertMissing('tfoot');
});
+ // Assert Distribution lists tab
+ $browser->assertSeeIn('@nav #tab-distlists', 'Distribution lists (1)')
+ ->click('@nav #tab-distlists')
+ ->with('@user-distlists table', function (Browser $browser) {
+ $browser->assertElementsCount('tbody tr', 1)
+ ->assertSeeIn('tbody tr:nth-child(1) td:first-child a', 'group-test@kolab.org')
+ ->assertVisible('tbody tr:nth-child(1) td:first-child svg.text-danger')
+ ->assertMissing('tfoot');
+ });
+
// Assert Users tab
$browser->assertSeeIn('@nav #tab-users', 'Users (4)')
->click('@nav #tab-users')
@@ -305,7 +325,7 @@
// Some tabs are loaded in background, wait a second
$browser->pause(500)
- ->assertElementsCount('@nav a', 5);
+ ->assertElementsCount('@nav a', 6);
// Note: Finances tab is tested in UserFinancesTest.php
$browser->assertSeeIn('@nav #tab-finances', 'Finances');
@@ -355,6 +375,14 @@
$browser->assertElementsCount('table tbody tr', 0)
->assertSeeIn('table tfoot tr td', 'There are no users in this account.');
});
+
+ // We don't expect John's distribution lists here
+ $browser->assertSeeIn('@nav #tab-distlists', 'Distribution lists (0)')
+ ->click('@nav #tab-distlists')
+ ->with('@user-distlists', function (Browser $browser) {
+ $browser->assertElementsCount('table tbody tr', 0)
+ ->assertSeeIn('table tfoot tr td', 'There are no distribution lists in this account.');
+ });
});
}
diff --git a/src/tests/Browser/Pages/Admin/User.php b/src/tests/Browser/Pages/Admin/Distlist.php
copy from src/tests/Browser/Pages/Admin/User.php
copy to src/tests/Browser/Pages/Admin/Distlist.php
--- a/src/tests/Browser/Pages/Admin/User.php
+++ b/src/tests/Browser/Pages/Admin/Distlist.php
@@ -4,18 +4,18 @@
use Laravel\Dusk\Page;
-class User extends Page
+class Distlist extends Page
{
- protected $userid;
+ protected $listid;
/**
* Object constructor.
*
- * @param int $userid User Id
+ * @param int $listid Distribution list Id
*/
- public function __construct($userid)
+ public function __construct($listid)
{
- $this->userid = $userid;
+ $this->listid = $listid;
}
/**
@@ -25,7 +25,7 @@
*/
public function url(): string
{
- return '/user/' . $this->userid;
+ return '/distlist/' . $this->listid;
}
/**
@@ -38,8 +38,7 @@
public function assert($browser): void
{
$browser->waitForLocation($this->url())
- ->waitUntilMissing('@app .app-loader')
- ->waitFor('@user-info');
+ ->waitFor('@distlist-info');
}
/**
@@ -51,13 +50,8 @@
{
return [
'@app' => '#app',
- '@user-info' => '#user-info',
- '@nav' => 'ul.nav-tabs',
- '@user-finances' => '#user-finances',
- '@user-aliases' => '#user-aliases',
- '@user-subscriptions' => '#user-subscriptions',
- '@user-domains' => '#user-domains',
- '@user-users' => '#user-users',
+ '@distlist-info' => '#distlist-info',
+ '@distlist-config' => '#distlist-config',
];
}
}
diff --git a/src/tests/Browser/Pages/Admin/User.php b/src/tests/Browser/Pages/Admin/User.php
--- a/src/tests/Browser/Pages/Admin/User.php
+++ b/src/tests/Browser/Pages/Admin/User.php
@@ -56,6 +56,7 @@
'@user-finances' => '#user-finances',
'@user-aliases' => '#user-aliases',
'@user-subscriptions' => '#user-subscriptions',
+ '@user-distlists' => '#user-distlists',
'@user-domains' => '#user-domains',
'@user-users' => '#user-users',
];
diff --git a/src/tests/Feature/Controller/Admin/GroupsTest.php b/src/tests/Feature/Controller/Admin/GroupsTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Feature/Controller/Admin/GroupsTest.php
@@ -0,0 +1,184 @@
+deleteTestGroup('group-test@kolab.org');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function tearDown(): void
+ {
+ $this->deleteTestGroup('group-test@kolab.org');
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test groups searching (/api/v4/groups)
+ */
+ public function testIndex(): void
+ {
+ $user = $this->getTestUser('john@kolab.org');
+ $admin = $this->getTestUser('jeroen@jeroen.jeroen');
+ $group = $this->getTestGroup('group-test@kolab.org');
+ $group->assignToWallet($user->wallets->first());
+
+ // Non-admin user
+ $response = $this->actingAs($user)->get("api/v4/groups");
+ $response->assertStatus(403);
+
+ // Search with no search criteria
+ $response = $this->actingAs($admin)->get("api/v4/groups");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame(0, $json['count']);
+ $this->assertSame([], $json['list']);
+
+ // Search with no matches expected
+ $response = $this->actingAs($admin)->get("api/v4/groups?search=john@kolab.org");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame(0, $json['count']);
+ $this->assertSame([], $json['list']);
+
+ // Search by email
+ $response = $this->actingAs($admin)->get("api/v4/groups?search={$group->email}");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame(1, $json['count']);
+ $this->assertCount(1, $json['list']);
+ $this->assertSame($group->email, $json['list'][0]['email']);
+
+ // Search by owner
+ $response = $this->actingAs($admin)->get("api/v4/groups?owner={$user->id}");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame(1, $json['count']);
+ $this->assertCount(1, $json['list']);
+ $this->assertSame($group->email, $json['list'][0]['email']);
+
+ // Search by owner (Ned is a controller on John's wallets,
+ // here we expect only domains assigned to Ned's wallet(s))
+ $ned = $this->getTestUser('ned@kolab.org');
+ $response = $this->actingAs($admin)->get("api/v4/groups?owner={$ned->id}");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame(0, $json['count']);
+ $this->assertCount(0, $json['list']);
+ }
+
+ /**
+ * Test group creating (POST /api/v4/groups)
+ */
+ public function testStore(): void
+ {
+ $user = $this->getTestUser('john@kolab.org');
+ $admin = $this->getTestUser('jeroen@jeroen.jeroen');
+
+ // Test unauthorized access to admin API
+ $response = $this->actingAs($user)->post("/api/v4/groups", []);
+ $response->assertStatus(403);
+
+ // Admin can't create groups
+ $response = $this->actingAs($admin)->post("/api/v4/groups", []);
+ $response->assertStatus(404);
+ }
+
+ /**
+ * Test group suspending (POST /api/v4/groups//suspend)
+ */
+ public function testSuspend(): void
+ {
+ Queue::fake(); // disable jobs
+
+ $user = $this->getTestUser('john@kolab.org');
+ $admin = $this->getTestUser('jeroen@jeroen.jeroen');
+ $group = $this->getTestGroup('group-test@kolab.org');
+ $group->assignToWallet($user->wallets->first());
+
+ // Test unauthorized access to admin API
+ $response = $this->actingAs($user)->post("/api/v4/groups/{$group->id}/suspend", []);
+ $response->assertStatus(403);
+
+ // Test non-existing group ID
+ $response = $this->actingAs($admin)->post("/api/v4/groups/abc/suspend", []);
+ $response->assertStatus(404);
+
+ $this->assertFalse($group->fresh()->isSuspended());
+
+ // Test suspending the group
+ $response = $this->actingAs($admin)->post("/api/v4/groups/{$group->id}/suspend", []);
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame('success', $json['status']);
+ $this->assertSame("Distribution list suspended successfully.", $json['message']);
+ $this->assertCount(2, $json);
+
+ $this->assertTrue($group->fresh()->isSuspended());
+ }
+
+ /**
+ * Test user un-suspending (POST /api/v4/users//unsuspend)
+ */
+ public function testUnsuspend(): void
+ {
+ Queue::fake(); // disable jobs
+
+ $user = $this->getTestUser('john@kolab.org');
+ $admin = $this->getTestUser('jeroen@jeroen.jeroen');
+ $group = $this->getTestGroup('group-test@kolab.org');
+ $group->assignToWallet($user->wallets->first());
+ $group->status |= Group::STATUS_SUSPENDED;
+ $group->save();
+
+ // Test unauthorized access to admin API
+ $response = $this->actingAs($user)->post("/api/v4/groups/{$group->id}/unsuspend", []);
+ $response->assertStatus(403);
+
+ // Invalid group ID
+ $response = $this->actingAs($admin)->post("/api/v4/groups/abc/unsuspend", []);
+ $response->assertStatus(404);
+
+ $this->assertTrue($group->fresh()->isSuspended());
+
+ // Test suspending the group
+ $response = $this->actingAs($admin)->post("/api/v4/groups/{$group->id}/unsuspend", []);
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame('success', $json['status']);
+ $this->assertSame("Distribution list unsuspended successfully.", $json['message']);
+ $this->assertCount(2, $json);
+
+ $this->assertFalse($group->fresh()->isSuspended());
+ }
+}
diff --git a/src/tests/Feature/Controller/Admin/UsersTest.php b/src/tests/Feature/Controller/Admin/UsersTest.php
--- a/src/tests/Feature/Controller/Admin/UsersTest.php
+++ b/src/tests/Feature/Controller/Admin/UsersTest.php
@@ -20,6 +20,7 @@
$this->deleteTestUser('UsersControllerTest1@userscontroller.com');
$this->deleteTestUser('test@testsearch.com');
$this->deleteTestDomain('testsearch.com');
+ $this->deleteTestGroup('group-test@kolab.org');
$jack = $this->getTestUser('jack@kolab.org');
$jack->setSetting('external_email', null);
@@ -47,6 +48,8 @@
{
$user = $this->getTestUser('john@kolab.org');
$admin = $this->getTestUser('jeroen@jeroen.jeroen');
+ $group = $this->getTestGroup('group-test@kolab.org');
+ $group->assignToWallet($user->wallets->first());
// Non-admin user
$response = $this->actingAs($user)->get("api/v4/users");
@@ -151,6 +154,17 @@
$this->assertSame(0, $json['count']);
$this->assertCount(0, $json['list']);
+ // Search by distribution list email
+ $response = $this->actingAs($admin)->get("api/v4/users?search=group-test@kolab.org");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame(1, $json['count']);
+ $this->assertCount(1, $json['list']);
+ $this->assertSame($user->id, $json['list'][0]['id']);
+ $this->assertSame($user->email, $json['list'][0]['email']);
+
// Deleted users/domains
$domain = $this->getTestDomain('testsearch.com', ['type' => \App\Domain::TYPE_EXTERNAL]);
$user = $this->getTestUser('test@testsearch.com');
diff --git a/src/tests/Feature/Jobs/Group/UpdateTest.php b/src/tests/Feature/Jobs/Group/UpdateTest.php
--- a/src/tests/Feature/Jobs/Group/UpdateTest.php
+++ b/src/tests/Feature/Jobs/Group/UpdateTest.php
@@ -2,7 +2,9 @@
namespace Tests\Feature\Jobs\Group;
+use App\Backends\LDAP;
use App\Group;
+use Illuminate\Support\Facades\Queue;
use Tests\TestCase;
class UpdateTest extends TestCase
@@ -31,19 +33,48 @@
*/
public function testHandle(): void
{
+ Queue::fake();
+
+ // Test non-existing group ID
+ $job = new \App\Jobs\Group\UpdateJob(123);
+ $job->handle();
+
+ $this->assertTrue($job->hasFailed());
+ $this->assertSame("Group 123 could not be found in the database.", $job->failureMessage);
+
+ // Create the group
$group = $this->getTestGroup('group@kolab.org', ['members' => []]);
+ LDAP::createGroup($group);
+
+ // Test if group properties (members) actually changed in LDAP
+ $group->members = ['test1@gmail.com'];
+ $group->status |= Group::STATUS_LDAP_READY;
+ $group->save();
$job = new \App\Jobs\Group\UpdateJob($group->id);
$job->handle();
- // TODO: Test if group properties (members) actually changed in LDAP
- $this->assertTrue(true);
+ $ldapGroup = LDAP::getGroup($group->email);
+ $root_dn = \config('ldap.hosted.root_dn');
- // Test non-existing group ID
- $job = new \App\Jobs\Group\UpdateJob(123);
+ $this->assertSame('uid=test1@gmail.com,ou=People,ou=kolab.org,' . $root_dn, $ldapGroup['uniquemember']);
+
+ // Test that suspended group is removed from LDAP
+ $group->suspend();
+
+ $job = new \App\Jobs\Group\UpdateJob($group->id);
$job->handle();
- $this->assertTrue($job->hasFailed());
- $this->assertSame("Group 123 could not be found in the database.", $job->failureMessage);
+ $this->assertNull(LDAP::getGroup($group->email));
+
+ // Test that unsuspended group is added back to LDAP
+ $group->unsuspend();
+
+ $job = new \App\Jobs\Group\UpdateJob($group->id);
+ $job->handle();
+
+ $ldapGroup = LDAP::getGroup($group->email);
+ $this->assertSame($group->email, $ldapGroup['mail']);
+ $this->assertSame('uid=test1@gmail.com,ou=People,ou=kolab.org,' . $root_dn, $ldapGroup['uniquemember']);
}
}