Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117917763
D2506.1775415826.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
37 KB
Referenced Files
None
Subscribers
None
D2506.1775415826.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D2506: [Admin] Distribution lists UI
Attached
Detach File
Event Timeline