diff --git a/src/app/Http/Controllers/API/V4/Reseller/UsersController.php b/src/app/Http/Controllers/API/V4/Reseller/UsersController.php index 91f301e0..88da04ca 100644 --- a/src/app/Http/Controllers/API/V4/Reseller/UsersController.php +++ b/src/app/Http/Controllers/API/V4/Reseller/UsersController.php @@ -1,114 +1,151 @@ user(); $search = trim(request()->input('search')); $owner = trim(request()->input('owner')); $result = collect([]); if ($owner) { - if ($owner = User::find($owner)) { - $result = $owner->users(false)->orderBy('email')->get(); + $owner = User::where('id', $owner) + ->where('tenant_id', $reseller->tenant_id) + ->whereNull('role') + ->first(); + + if ($owner) { + $result = $owner->users(false)->whereNull('role')->orderBy('email')->get(); } } elseif (strpos($search, '@')) { // Search by email - if ($user = User::findByEmail($search, false)) { - $result->push($user); - } else { - // Search by an external email - // TODO: This is not optimal (external email should be in users table) - $user_ids = UserSetting::where('key', 'external_email')->where('value', $search) - ->get()->pluck('user_id'); + $result = User::withTrashed()->where('email', $search) + ->where('tenant_id', $reseller->tenant_id) + ->whereNull('role') + ->orderBy('email') + ->get(); + + if ($result->isEmpty()) { + // Search by an alias + $user_ids = UserAlias::where('alias', $search)->get()->pluck('user_id'); - // TODO: Sort order - $result = User::find($user_ids); + // Search by an external email + $ext_user_ids = UserSetting::where('key', 'external_email') + ->where('value', $search) + ->get() + ->pluck('user_id'); + + $user_ids = $user_ids->merge($ext_user_ids)->unique(); + + if (!$user_ids->isEmpty()) { + $result = User::withTrashed()->whereIn('id', $user_ids) + ->where('tenant_id', $reseller->tenant_id) + ->whereNull('role') + ->orderBy('email') + ->get(); + } } } elseif (is_numeric($search)) { // Search by user ID - if ($user = User::find($search)) { + $user = User::withTrashed()->where('id', $search) + ->where('tenant_id', $reseller->tenant_id) + ->whereNull('role') + ->first(); + + if ($user) { $result->push($user); } } elseif (!empty($search)) { // Search by domain - if ($domain = Domain::where('namespace', $search)->first()) { - if ($wallet = $domain->wallet()) { - $result->push($wallet->owner); + $domain = Domain::withTrashed()->where('namespace', $search) + ->where('tenant_id', $reseller->tenant_id) + ->first(); + + if ($domain) { + if ( + ($wallet = $domain->wallet()) + && ($owner = $wallet->owner()->withTrashed()->first()) + && $owner->tenant_id == $reseller->tenant_id + && empty($owner->role) + ) { + $result->push($owner); } } } // Process the result $result = $result->map(function ($user) { $data = $user->toArray(); $data = array_merge($data, self::userStatuses($user)); return $data; }); $result = [ 'list' => $result, 'count' => count($result), 'message' => \trans('app.search-foundxusers', ['x' => count($result)]), ]; return response()->json($result); } /** * Update user data. * * @param \Illuminate\Http\Request $request The API request. * @params string $id User identifier * * @return \Illuminate\Http\JsonResponse The response */ public function update(Request $request, $id) { + $reseller = auth()->user(); $user = User::find($id); - if (empty($user)) { + if (empty($user) || $user->tenant_id != $reseller->tenant_id || $user->role == 'admin') { return $this->errorResponse(404); } // For now admins can change only user external email address $rules = []; if (array_key_exists('external_email', $request->input())) { $rules['external_email'] = 'email'; } // Validate input $v = Validator::make($request->all(), $rules); if ($v->fails()) { return response()->json(['status' => 'error', 'errors' => $v->errors()], 422); } // Update user settings $settings = $request->only(array_keys($rules)); if (!empty($settings)) { $user->setSettings($settings); } return response()->json([ 'status' => 'success', 'message' => __('app.user-update-success'), ]); } } diff --git a/src/app/Http/Middleware/AuthenticateReseller.php b/src/app/Http/Middleware/AuthenticateReseller.php index ba49549f..829e0734 100644 --- a/src/app/Http/Middleware/AuthenticateReseller.php +++ b/src/app/Http/Middleware/AuthenticateReseller.php @@ -1,30 +1,34 @@ user(); if (!$user) { abort(403, "Unauthorized"); } if ($user->role !== "reseller") { abort(403, "Unauthorized"); } + if ($user->tenant_id != \config('app.tenant_id')) { + abort(403, "Unauthorized"); + } + return $next($request); } } diff --git a/src/tests/Feature/Controller/Reseller/UsersTest.php b/src/tests/Feature/Controller/Reseller/UsersTest.php new file mode 100644 index 00000000..e2981f0d --- /dev/null +++ b/src/tests/Feature/Controller/Reseller/UsersTest.php @@ -0,0 +1,290 @@ + 1]); + + // $this->deleteTestUser('UsersControllerTest1@userscontroller.com'); + $this->deleteTestUser('test@testsearch.com'); + $this->deleteTestDomain('testsearch.com'); + } + + /** + * {@inheritDoc} + */ + public function tearDown(): void + { + // $this->deleteTestUser('UsersControllerTest1@userscontroller.com'); + $this->deleteTestUser('test@testsearch.com'); + $this->deleteTestDomain('testsearch.com'); + + \config(['app.tenant_id' => 1]); + + parent::tearDown(); + } + + /** + * Test users searching (/api/v4/users) + */ + public function testIndex(): void + { + Queue::fake(); + + $user = $this->getTestUser('john@kolab.org'); + $admin = $this->getTestUser('jeroen@jeroen.jeroen'); + $reseller = $this->getTestUser('reseller@reseller.com'); + $reseller2 = $this->getTestUser('test@reseller.com'); + $tenant = Tenant::where('title', 'Sample Tenant')->first(); + $tenant2 = Tenant::where('title', 'Kolab Now')->first(); + + $reseller2->tenant_id = $tenant2->id; + $reseller2->role = 'reseller'; + $reseller2->save(); + + \config(['app.tenant_id' => $tenant->id]); + + // Normal user + $response = $this->actingAs($user)->get("api/v4/users"); + $response->assertStatus(403); + + // Admin user + $response = $this->actingAs($admin)->get("api/v4/users"); + $response->assertStatus(403); + + // Reseller from another tenant + $response = $this->actingAs($reseller2)->get("api/v4/users"); + $response->assertStatus(403); + + // Search with no search criteria + $response = $this->actingAs($reseller)->get("api/v4/users"); + $response->assertStatus(200); + + $json = $response->json(); + + $this->assertSame(0, $json['count']); + $this->assertSame([], $json['list']); + + // Search with no matches expected + $response = $this->actingAs($reseller)->get("api/v4/users?search=abcd1234efgh5678"); + $response->assertStatus(200); + + $json = $response->json(); + + $this->assertSame(0, $json['count']); + $this->assertSame([], $json['list']); + + // Search by domain in another tenant + $response = $this->actingAs($reseller)->get("api/v4/users?search=kolab.org"); + $response->assertStatus(200); + + $json = $response->json(); + + $this->assertSame(0, $json['count']); + $this->assertSame([], $json['list']); + + // Search by user ID in another tenant + $response = $this->actingAs($reseller)->get("api/v4/users?search={$user->id}"); + $response->assertStatus(200); + + $json = $response->json(); + + $this->assertSame(0, $json['count']); + $this->assertSame([], $json['list']); + + // Search by email (primary) - existing user in another tenant + $response = $this->actingAs($reseller)->get("api/v4/users?search=john@kolab.org"); + $response->assertStatus(200); + + $json = $response->json(); + + $this->assertSame(0, $json['count']); + $this->assertSame([], $json['list']); + + // Search by owner - existing user in another tenant + $response = $this->actingAs($reseller)->get("api/v4/users?owner={$user->id}"); + $response->assertStatus(200); + + $json = $response->json(); + + $this->assertSame(0, $json['count']); + $this->assertSame([], $json['list']); + + // Create a domain with some users in the Sample Tenant so we have anything to search for + $domain = $this->getTestDomain('testsearch.com', ['type' => \App\Domain::TYPE_EXTERNAL]); + $domain->tenant_id = $tenant->id; + $domain->save(); + $user = $this->getTestUser('test@testsearch.com'); + $user->tenant_id = $tenant->id; + $user->save(); + $plan = \App\Plan::where('title', 'group')->first(); + $user->assignPlan($plan, $domain); + $user->setAliases(['alias@testsearch.com']); + $user->setSetting('external_email', 'john.doe.external@gmail.com'); + + // Search by domain + $response = $this->actingAs($reseller)->get("api/v4/users?search=testsearch.com"); + $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']); + + // Search by user ID + $response = $this->actingAs($reseller)->get("api/v4/users?search={$user->id}"); + $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']); + + // Search by email (primary) - existing user in reseller's tenant + $response = $this->actingAs($reseller)->get("api/v4/users?search=test@testsearch.com"); + $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']); + + // Search by email (alias) + $response = $this->actingAs($reseller)->get("api/v4/users?search=alias@testsearch.com"); + $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']); + + // Search by email (external), there are two users with this email, but only one + // in the reseller's tenant + $response = $this->actingAs($reseller)->get("api/v4/users?search=john.doe.external@gmail.com"); + $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']); + + // Search by owner + $response = $this->actingAs($reseller)->get("api/v4/users?owner={$user->id}"); + $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 + $user->delete(); + + $response = $this->actingAs($reseller)->get("api/v4/users?search=test@testsearch.com"); + $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']); + $this->assertTrue($json['list'][0]['isDeleted']); + + $response = $this->actingAs($reseller)->get("api/v4/users?search=alias@testsearch.com"); + $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']); + $this->assertTrue($json['list'][0]['isDeleted']); + + $response = $this->actingAs($reseller)->get("api/v4/users?search=testsearch.com"); + $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']); + $this->assertTrue($json['list'][0]['isDeleted']); + } + + /** + * Test user update (PUT /api/v4/users/) + */ + public function testUpdate(): void + { + $this->markTestIncomplete(); +/* + $user = $this->getTestUser('UsersControllerTest1@userscontroller.com'); + $admin = $this->getTestUser('jeroen@jeroen.jeroen'); + + // Test unauthorized access to admin API + $response = $this->actingAs($user)->put("/api/v4/users/{$user->id}", []); + $response->assertStatus(403); + + // Test updatig the user data (empty data) + $response = $this->actingAs($admin)->put("/api/v4/users/{$user->id}", []); + $response->assertStatus(200); + + $json = $response->json(); + + $this->assertSame('success', $json['status']); + $this->assertSame("User data updated successfully.", $json['message']); + $this->assertCount(2, $json); + + // Test error handling + $post = ['external_email' => 'aaa']; + $response = $this->actingAs($admin)->put("/api/v4/users/{$user->id}", $post); + $response->assertStatus(422); + + $json = $response->json(); + + $this->assertSame('error', $json['status']); + $this->assertSame("The external email must be a valid email address.", $json['errors']['external_email'][0]); + $this->assertCount(2, $json); + + // Test real update + $post = ['external_email' => 'modified@test.com']; + $response = $this->actingAs($admin)->put("/api/v4/users/{$user->id}", $post); + $response->assertStatus(200); + + $json = $response->json(); + + $this->assertSame('success', $json['status']); + $this->assertSame("User data updated successfully.", $json['message']); + $this->assertCount(2, $json); + $this->assertSame('modified@test.com', $user->getSetting('external_email')); +*/ + } +}