diff --git a/src/app/Http/Controllers/API/DomainsController.php b/src/app/Http/Controllers/API/DomainsController.php index 432ef021..ecaad33b 100644 --- a/src/app/Http/Controllers/API/DomainsController.php +++ b/src/app/Http/Controllers/API/DomainsController.php @@ -1,213 +1,216 @@ confirm()) { return response()->json(['status' => 'error']); } - return response()->json(['status' => 'success']); + return response()->json([ + 'status' => 'success', + 'message' => __('app.domain-verify-success'), + ]); } /** * Remove the specified resource from storage. * * @param int $id * * @return \Illuminate\Http\Response */ public function destroy($id) { // } /** * Show the form for editing the specified resource. * * @param int $id * * @return \Illuminate\Http\Response */ public function edit($id) { // } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * * @return \Illuminate\Http\Response */ public function store(Request $request) { // } /** * Get the information about the specified domain. * * @param int $id Domain identifier * * @return \Illuminate\Http\Response */ public function show($id) { $domain = Domain::findOrFail($id); // Only owner (or admin) has access to the domain if (!self::hasAccess($domain)) { return abort(403); } $response = $domain->toArray(); // Add hash information to the response $response['hash_text'] = $domain->hash(Domain::HASH_TEXT); $response['hash_cname'] = $domain->hash(Domain::HASH_CNAME); $response['hash_code'] = $domain->hash(Domain::HASH_CODE); // Add DNS/MX configuration for the domain $response['dns'] = self::getDNSConfig($domain); $response['config'] = self::getMXConfig($domain->namespace); $response['confirmed'] = $domain->isConfirmed(); return response()->json($response); } /** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param int $id * * @return \Illuminate\Http\Response */ public function update(Request $request, $id) { // } /** * Provide DNS MX information to configure specified domain for */ protected static function getMXConfig(string $namespace): array { $entries = []; // copy MX entries from an existing domain if ($master = \config('dns.copyfrom')) { // TODO: cache this lookup foreach ((array) dns_get_record($master, DNS_MX) as $entry) { $entries[] = sprintf( "@\t%s\t%s\tMX\t%d %s.", \config('dns.ttl', $entry['ttl']), $entry['class'], $entry['pri'], $entry['target'] ); } } elseif ($static = \config('dns.static')) { $entries[] = strtr($static, array('\n' => "\n", '%s' => $namespace)); } // display SPF settings if ($spf = \config('dns.spf')) { $entries[] = ';'; foreach (['TXT', 'SPF'] as $type) { $entries[] = sprintf( "@\t%s\tIN\t%s\t\"%s\"", \config('dns.ttl'), $type, $spf ); } } return $entries; } /** * Provide sample DNS config for domain confirmation */ protected static function getDNSConfig(Domain $domain): array { $serial = date('Ymd01'); $hash_txt = $domain->hash(Domain::HASH_TEXT); $hash_cname = $domain->hash(Domain::HASH_CNAME); $hash = $domain->hash(Domain::HASH_CODE); return [ "@ IN SOA ns1.dnsservice.com. hostmaster.{$domain->namespace}. (", " {$serial} 10800 3600 604800 86400 )", ";", "@ IN A ", "www IN A ", ";", "{$hash_cname}.{$domain->namespace}. IN CNAME {$hash}.{$domain->namespace}.", "@ 3600 TXT \"{$hash_txt}\"", ]; } /** * Check if the current user has access to the domain * * @param \App\Domain Domain * * @return bool True if current user has access, False otherwise */ protected static function hasAccess(Domain $domain): bool { $user = Auth::guard()->user(); $entitlement = $domain->entitlement()->first(); // TODO: Admins return $entitlement && $entitlement->owner_id == $user->id; } } diff --git a/src/resources/lang/en/app.php b/src/resources/lang/en/app.php index 51e48941..0a6006b8 100644 --- a/src/resources/lang/en/app.php +++ b/src/resources/lang/en/app.php @@ -1,22 +1,24 @@ 'Choose :plan', 'process-user-new' => 'User registered', 'process-user-ldap-ready' => 'User created', 'process-user-imap-ready' => 'User mailbox created', 'process-domain-new' => 'Custom domain registered', 'process-domain-ldap-ready' => 'Custom domain created', 'process-domain-verified' => 'Custom domain verified', 'process-domain-confirmed' => 'Custom domain ownership verified', + + 'domain-verify-success' => 'Domain verified successfully', ]; diff --git a/src/resources/vue/components/Domain.vue b/src/resources/vue/components/Domain.vue index 1e25cb63..43a88b50 100644 --- a/src/resources/vue/components/Domain.vue +++ b/src/resources/vue/components/Domain.vue @@ -1,82 +1,83 @@ diff --git a/src/tests/Browser/DomainTest.php b/src/tests/Browser/DomainTest.php index cbd34234..90dc43bc 100644 --- a/src/tests/Browser/DomainTest.php +++ b/src/tests/Browser/DomainTest.php @@ -1,86 +1,92 @@ browse(function (Browser $browser) { $browser->visit('/domain/123')->on(new Home()); }); } /** * Test domain info page (non-existing domain id) */ public function testDomainInfo404(): void { $this->browse(function (Browser $browser) { // FIXME: I couldn't make loginAs() method working // Note: Here we're also testing that unauthenticated request // is passed to logon form and then "redirected" to the requested page $browser->visit('/domain/123') ->on(new Home()) ->submitLogon('john@kolab.org', 'simple123') // TODO: the check below could look simpler, but we can't // just remove the callback argument. We'll create // Browser wrappen in future, then we could create expectError() method ->with(new Error('404'), function (Browser $browser) { }); }); } /** * Test domain info page (existing domain) * * @depends testDomainInfo404 */ public function testDomainInfo(): void { $this->browse(function (Browser $browser) { // Unconfirmed domain $domain = Domain::where('namespace', 'kolab.org')->first(); $domain->status ^= Domain::STATUS_CONFIRMED; $domain->save(); $browser->visit('/domain/' . $domain->id) ->on(new DomainPage()) ->whenAvailable('@verify', function (Browser $browser) use ($domain) { // Make sure the domain is confirmed now // TODO: Test verification process failure $domain->status |= Domain::STATUS_CONFIRMED; $domain->save(); $browser->assertSeeIn('pre', $domain->namespace) ->assertSeeIn('pre', $domain->hash()) ->click('button'); }) ->whenAvailable('@config', function (Browser $browser) use ($domain) { $browser->assertSeeIn('pre', $domain->namespace); }) - ->assertMissing('@verify'); + ->assertMissing('@verify') + ->with(new Toast(Toast::TYPE_SUCCESS), function (Browser $browser) { + $browser->assertToastTitle('') + ->assertToastMessage('Domain verified successfully') + ->closeToast(); + }); // Check that confirmed domain page contains only the config box $browser->visit('/domain/' . $domain->id) ->on(new DomainPage()) ->assertMissing('@verify') ->assertPresent('@config'); }); } } diff --git a/src/tests/Feature/Controller/DomainsTest.php b/src/tests/Feature/Controller/DomainsTest.php index 5e6f0ff7..01e00655 100644 --- a/src/tests/Feature/Controller/DomainsTest.php +++ b/src/tests/Feature/Controller/DomainsTest.php @@ -1,112 +1,113 @@ delete(); Domain::where('namespace', 'domainscontroller.com')->delete(); } /** * Test domain confirm request */ public function testConfirm(): void { $sku_domain = Sku::where('title', 'domain')->first(); $user = $this->getTestUser('test1@domainscontroller.com'); $domain = $this->getTestDomain('domainscontroller.com', [ 'status' => Domain::STATUS_NEW, 'type' => Domain::TYPE_EXTERNAL, ]); // No entitlement (user has no access to this domain), expect 403 $response = $this->actingAs($user)->get("api/v4/domains/{$domain->id}/confirm"); $response->assertStatus(403); Entitlement::create([ 'owner_id' => $user->id, 'wallet_id' => $user->wallets()->first()->id, 'sku_id' => $sku_domain->id, 'entitleable_id' => $domain->id, 'entitleable_type' => Domain::class ]); $response = $this->actingAs($user)->get("api/v4/domains/{$domain->id}/confirm"); $response->assertStatus(200); $json = $response->json(); $this->assertEquals('error', $json['status']); $domain->status |= Domain::STATUS_CONFIRMED; $domain->save(); $response = $this->actingAs($user)->get("api/v4/domains/{$domain->id}/confirm"); $response->assertStatus(200); $json = $response->json(); $this->assertEquals('success', $json['status']); + $this->assertEquals('Domain verified successfully', $json['message']); } /** * Test fetching domain info */ public function testShow(): void { $sku_domain = Sku::where('title', 'domain')->first(); $user = $this->getTestUser('test1@domainscontroller.com'); $domain = $this->getTestDomain('domainscontroller.com', [ 'status' => Domain::STATUS_NEW, 'type' => Domain::TYPE_EXTERNAL, ]); // No entitlement (user has no access to this domain), expect 403 $response = $this->actingAs($user)->get("api/v4/domains/{$domain->id}"); $response->assertStatus(403); Entitlement::create([ 'owner_id' => $user->id, 'wallet_id' => $user->wallets()->first()->id, 'sku_id' => $sku_domain->id, 'entitleable_id' => $domain->id, 'entitleable_type' => Domain::class ]); $response = $this->actingAs($user)->get("api/v4/domains/{$domain->id}"); $response->assertStatus(200); $json = $response->json(); $this->assertEquals($domain->id, $json['id']); $this->assertEquals($domain->namespace, $json['namespace']); $this->assertEquals($domain->status, $json['status']); $this->assertEquals($domain->type, $json['type']); $this->assertTrue($json['confirmed'] === false); $this->assertSame($domain->hash(Domain::HASH_TEXT), $json['hash_text']); $this->assertSame($domain->hash(Domain::HASH_CNAME), $json['hash_cname']); $this->assertSame($domain->hash(Domain::HASH_CODE), $json['hash_code']); $this->assertCount(4, $json['config']); $this->assertTrue(strpos(implode("\n", $json['config']), $domain->namespace) !== false); $this->assertCount(8, $json['dns']); $this->assertTrue(strpos(implode("\n", $json['dns']), $domain->namespace) !== false); $this->assertTrue(strpos(implode("\n", $json['dns']), $domain->hash()) !== false); } }