Page MenuHomePhorge

D2506.1775415826.diff
No OneTemporary

Authored By
Unknown
Size
37 KB
Referenced Files
None
Subscribers
None

D2506.1775415826.diff

diff --git a/src/app/Http/Controllers/API/V4/Admin/GroupsController.php b/src/app/Http/Controllers/API/V4/Admin/GroupsController.php
new file mode 100644
--- /dev/null
+++ b/src/app/Http/Controllers/API/V4/Admin/GroupsController.php
@@ -0,0 +1,118 @@
+<?php
+
+namespace App\Http\Controllers\API\V4\Admin;
+
+use App\Group;
+use App\User;
+use Illuminate\Http\Request;
+
+class GroupsController extends \App\Http\Controllers\API\V4\GroupsController
+{
+ /**
+ * Search for groups
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function index()
+ {
+ $search = trim(request()->input('search'));
+ $owner = trim(request()->input('owner'));
+ $result = collect([]);
+
+ if ($owner) {
+ if ($owner = User::find($owner)) {
+ foreach ($owner->wallets as $wallet) {
+ $wallet->entitlements()->where('entitleable_type', Group::class)->get()
+ ->each(function ($entitlement) use ($result) {
+ $result->push($entitlement->entitleable);
+ });
+ }
+
+ $result = $result->sortBy('namespace')->values();
+ }
+ } elseif (!empty($search)) {
+ if ($group = Group::where('email', $search)->first()) {
+ $result->push($group);
+ }
+ }
+
+ // Process the result
+ $result = $result->map(function ($group) {
+ $data = [
+ 'id' => $group->id,
+ 'email' => $group->email,
+ ];
+
+ $data = array_merge($data, self::groupStatuses($group));
+ return $data;
+ });
+
+ $result = [
+ 'list' => $result,
+ 'count' => count($result),
+ 'message' => \trans('app.search-foundxdistlists', ['x' => count($result)]),
+ ];
+
+ return response()->json($result);
+ }
+
+ /**
+ * Create a new group.
+ *
+ * @param \Illuminate\Http\Request $request The API request.
+ *
+ * @return \Illuminate\Http\JsonResponse The response
+ */
+ public function store(Request $request)
+ {
+ return $this->errorResponse(404);
+ }
+
+ /**
+ * Suspend a group
+ *
+ * @param \Illuminate\Http\Request $request The API request.
+ * @param string $id Group identifier
+ *
+ * @return \Illuminate\Http\JsonResponse The response
+ */
+ public function suspend(Request $request, $id)
+ {
+ $group = Group::find($id);
+
+ if (empty($group)) {
+ return $this->errorResponse(404);
+ }
+
+ $group->suspend();
+
+ return response()->json([
+ 'status' => 'success',
+ 'message' => __('app.distlist-suspend-success'),
+ ]);
+ }
+
+ /**
+ * Un-Suspend a group
+ *
+ * @param \Illuminate\Http\Request $request The API request.
+ * @param string $id Group identifier
+ *
+ * @return \Illuminate\Http\JsonResponse The response
+ */
+ public function unsuspend(Request $request, $id)
+ {
+ $group = Group::find($id);
+
+ if (empty($group)) {
+ return $this->errorResponse(404);
+ }
+
+ $group->unsuspend();
+
+ return response()->json([
+ 'status' => 'success',
+ 'message' => __('app.distlist-unsuspend-success'),
+ ]);
+ }
+}
diff --git a/src/app/Http/Controllers/API/V4/Admin/UsersController.php b/src/app/Http/Controllers/API/V4/Admin/UsersController.php
--- a/src/app/Http/Controllers/API/V4/Admin/UsersController.php
+++ b/src/app/Http/Controllers/API/V4/Admin/UsersController.php
@@ -3,6 +3,7 @@
namespace App\Http\Controllers\API\V4\Admin;
use App\Domain;
+use App\Group;
use App\Sku;
use App\User;
use App\UserAlias;
@@ -42,6 +43,11 @@
$user_ids = $user_ids->merge($ext_user_ids)->unique();
+ // Search by a distribution list email
+ if ($group = Group::withTrashed()->where('email', $search)->first()) {
+ $user_ids = $user_ids->merge([$group->wallet()->user_id])->unique();
+ }
+
if (!$user_ids->isEmpty()) {
$result = User::withTrashed()->whereIn('id', $user_ids)
->orderBy('email')->get();
diff --git a/src/app/Jobs/Group/UpdateJob.php b/src/app/Jobs/Group/UpdateJob.php
--- a/src/app/Jobs/Group/UpdateJob.php
+++ b/src/app/Jobs/Group/UpdateJob.php
@@ -2,6 +2,7 @@
namespace App\Jobs\Group;
+use App\Backends\LDAP;
use App\Jobs\GroupJob;
class UpdateJob extends GroupJob
@@ -19,11 +20,29 @@
return;
}
- if (!$group->isLdapReady()) {
+ // Cancel the update if the group is deleted or not yet in LDAP
+ if (!$group->isLdapReady() || $group->isDeleted()) {
$this->delete();
return;
}
- \App\Backends\LDAP::updateGroup($group);
+ LDAP::connect();
+
+ // Groups does not have an attribute for the status, therefore
+ // we remove suspended groups from LDAP.
+ // We do not remove STATUS_LDAP_READY flag because it is part of the
+ // setup process.
+
+ $inLdap = !empty(LDAP::getGroup($group->email));
+
+ if ($group->isSuspended() && $inLdap) {
+ LDAP::deleteGroup($group);
+ } elseif (!$group->isSuspended() && !$inLdap) {
+ LDAP::createGroup($group);
+ } else {
+ LDAP::updateGroup($group);
+ }
+
+ LDAP::disconnect();
}
}
diff --git a/src/resources/js/routes-admin.js b/src/resources/js/routes-admin.js
--- a/src/resources/js/routes-admin.js
+++ b/src/resources/js/routes-admin.js
@@ -1,4 +1,5 @@
import DashboardComponent from '../vue/Admin/Dashboard'
+import DistlistComponent from '../vue/Admin/Distlist'
import DomainComponent from '../vue/Admin/Domain'
import LoginComponent from '../vue/Login'
import LogoutComponent from '../vue/Logout'
@@ -18,6 +19,12 @@
meta: { requiresAuth: true }
},
{
+ path: '/distlist/:list',
+ name: 'distlist',
+ component: DistlistComponent,
+ meta: { requiresAuth: true }
+ },
+ {
path: '/domain/:domain',
name: 'domain',
component: DomainComponent,
diff --git a/src/resources/lang/en/app.php b/src/resources/lang/en/app.php
--- a/src/resources/lang/en/app.php
+++ b/src/resources/lang/en/app.php
@@ -38,6 +38,8 @@
'distlist-update-success' => 'Distribution list updated successfully.',
'distlist-create-success' => 'Distribution list created successfully.',
'distlist-delete-success' => 'Distribution list deleted successfully.',
+ 'distlist-suspend-success' => 'Distribution list suspended successfully.',
+ 'distlist-unsuspend-success' => 'Distribution list unsuspended successfully.',
'domain-verify-success' => 'Domain verified successfully.',
'domain-verify-error' => 'Domain ownership verification failed.',
@@ -52,6 +54,7 @@
'user-reset-2fa-success' => '2-Factor authentication reset successfully.',
'search-foundxdomains' => ':x domains have been found.',
+ 'search-foundxgroups' => ':x distribution lists have been found.',
'search-foundxusers' => ':x user accounts have been found.',
'support-request-success' => 'Support request submitted successfully.',
diff --git a/src/resources/vue/Admin/Distlist.vue b/src/resources/vue/Admin/Distlist.vue
new file mode 100644
--- /dev/null
+++ b/src/resources/vue/Admin/Distlist.vue
@@ -0,0 +1,79 @@
+<template>
+ <div v-if="list.id" class="container">
+ <div class="card" id="distlist-info">
+ <div class="card-body">
+ <div class="card-title">{{ list.email }}</div>
+ <div class="card-text">
+ <form class="read-only short">
+ <div class="form-group row">
+ <label for="distlistid" class="col-sm-4 col-form-label">ID <span class="text-muted">(Created at)</span></label>
+ <div class="col-sm-8">
+ <span class="form-control-plaintext" id="distlistid">
+ {{ list.id }} <span class="text-muted">({{ list.created_at }})</span>
+ </span>
+ </div>
+ </div>
+ <div class="form-group row">
+ <label for="status" class="col-sm-4 col-form-label">Status</label>
+ <div class="col-sm-8">
+ <span :class="$root.distlistStatusClass(list) + ' form-control-plaintext'" id="status">{{ $root.distlistStatusText(list) }}</span>
+ </div>
+ </div>
+ <div class="form-group row">
+ <label for="members-input" class="col-sm-4 col-form-label">Recipients</label>
+ <div class="col-sm-8">
+ <span class="form-control-plaintext" id="members">
+ <span v-for="member in list.members" :key="member">{{ member }}<br></span>
+ </span>
+ </div>
+ </div>
+ </form>
+ <div class="mt-2">
+ <button v-if="!list.isSuspended" id="button-suspend" class="btn btn-warning" type="button" @click="suspendList">Suspend</button>
+ <button v-if="list.isSuspended" id="button-unsuspend" class="btn btn-warning" type="button" @click="unsuspendList">Unsuspend</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script>
+ export default {
+ data() {
+ return {
+ list: { members: [] }
+ }
+ },
+ created() {
+ this.$root.startLoading()
+
+ axios.get('/api/v4/groups/' + this.$route.params.list)
+ .then(response => {
+ this.$root.stopLoading()
+ this.list = response.data
+ })
+ .catch(this.$root.errorHandler)
+ },
+ methods: {
+ suspendList() {
+ axios.post('/api/v4/groups/' + this.list.id + '/suspend', {})
+ .then(response => {
+ if (response.data.status == 'success') {
+ this.$toast.success(response.data.message)
+ this.list = Object.assign({}, this.list, { isSuspended: true })
+ }
+ })
+ },
+ unsuspendList() {
+ axios.post('/api/v4/groups/' + this.list.id + '/unsuspend', {})
+ .then(response => {
+ if (response.data.status == 'success') {
+ this.$toast.success(response.data.message)
+ this.list = Object.assign({}, this.list, { isSuspended: false })
+ }
+ })
+ }
+ }
+ }
+</script>
diff --git a/src/resources/vue/Admin/Domain.vue b/src/resources/vue/Admin/Domain.vue
--- a/src/resources/vue/Admin/Domain.vue
+++ b/src/resources/vue/Admin/Domain.vue
@@ -4,8 +4,8 @@
<div class="card-body">
<div class="card-title">{{ domain.namespace }}</div>
<div class="card-text">
- <form>
- <div class="form-group row mb-0">
+ <form class="read-only short">
+ <div class="form-group row">
<label for="domainid" class="col-sm-4 col-form-label">ID <span class="text-muted">(Created at)</span></label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="domainid">
@@ -13,7 +13,7 @@
</span>
</div>
</div>
- <div class="form-group row mb-0">
+ <div class="form-group row">
<label for="first_name" class="col-sm-4 col-form-label">Status</label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="status">
diff --git a/src/resources/vue/Admin/User.vue b/src/resources/vue/Admin/User.vue
--- a/src/resources/vue/Admin/User.vue
+++ b/src/resources/vue/Admin/User.vue
@@ -108,6 +108,11 @@
Users ({{ users.length }})
</a>
</li>
+ <li class="nav-item">
+ <a class="nav-link" id="tab-distlists" href="#user-distlists" role="tab" aria-controls="user-distlists" aria-selected="false">
+ Distribution lists ({{ distlists.length }})
+ </a>
+ </li>
</ul>
<div class="tab-content">
<div class="tab-pane show active" id="user-finances" role="tabpanel" aria-labelledby="tab-finances">
@@ -259,6 +264,32 @@
</div>
</div>
</div>
+ <div class="tab-pane" id="user-distlists" role="tabpanel" aria-labelledby="tab-distlists">
+ <div class="card-body">
+ <div class="card-text">
+ <table class="table table-sm table-hover">
+ <thead class="thead-light">
+ <tr>
+ <th scope="col">Email address</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr v-for="list in distlists" :key="list.id" @click="$root.clickRecord">
+ <td>
+ <svg-icon icon="users" :class="$root.distlistStatusClass(list)" :title="$root.distlistStatusText(list)"></svg-icon>
+ <router-link :to="{ path: '/distlist/' + list.id }">{{ list.email }}</router-link>
+ </td>
+ </tr>
+ </tbody>
+ <tfoot class="table-fake-body">
+ <tr>
+ <td>There are no distribution lists in this account.</td>
+ </tr>
+ </tfoot>
+ </table>
+ </div>
+ </div>
+ </div>
</div>
<div id="discount-dialog" class="modal" tabindex="-1" role="dialog">
@@ -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 @@
+<?php
+
+namespace Tests\Browser\Admin;
+
+use App\Group;
+use Illuminate\Support\Facades\Queue;
+use Tests\Browser;
+use Tests\Browser\Components\Toast;
+use Tests\Browser\Pages\Admin\Distlist as DistlistPage;
+use Tests\Browser\Pages\Admin\User as UserPage;
+use Tests\Browser\Pages\Dashboard;
+use Tests\Browser\Pages\Home;
+use Tests\TestCaseDusk;
+
+class DistlistTest extends TestCaseDusk
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ self::useAdminUrl();
+
+ $this->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 @@
+<?php
+
+namespace Tests\Feature\Controller\Admin;
+
+use App\Group;
+use Illuminate\Support\Facades\Queue;
+use Tests\TestCase;
+
+class GroupsTest extends TestCase
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ self::useAdminUrl();
+
+ $this->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/<group-id>/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/<user-id>/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']);
}
}

File Metadata

Mime Type
text/plain
Expires
Sun, Apr 5, 7:03 PM (1 d, 14 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18834225
Default Alt Text
D2506.1775415826.diff (37 KB)

Event Timeline