From 8e7b53fd269d447f5bf5056c93f9df4b2d539036 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 20 Feb 2018 21:14:21 +1100 Subject: [PATCH] Refactor Cloudflare DNS to add and remove A records for custom domains (#1905) * Refactor Cloudflare DNS to add and remove A records for custom domains * rebase --- app/Events/SubdomainWasRemoved.php | 21 ++++ app/Http/Controllers/AccountController.php | 38 ++++--- app/Listeners/DNSListener.php | 8 ++ app/Ninja/DNS/Cloudflare.php | 116 +++++++++++++++++---- app/Providers/EventServiceProvider.php | 7 +- 5 files changed, 155 insertions(+), 35 deletions(-) create mode 100644 app/Events/SubdomainWasRemoved.php diff --git a/app/Events/SubdomainWasRemoved.php b/app/Events/SubdomainWasRemoved.php new file mode 100644 index 000000000000..ef0a1a2a969c --- /dev/null +++ b/app/Events/SubdomainWasRemoved.php @@ -0,0 +1,21 @@ +account = $account; + } +} diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 7a274051be91..f60044a525ca 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Events\SubdomainWasRemoved; use App\Events\SubdomainWasUpdated; use App\Events\UserSettingsChanged; use App\Events\UserSignedUp; @@ -630,10 +631,10 @@ class AccountController extends BaseController // sample invoice to help determine variables $invoice = Invoice::scope() - ->invoiceType(INVOICE_TYPE_STANDARD) - ->with('client', 'account') - ->where('is_recurring', '=', false) - ->first(); + ->invoiceType(INVOICE_TYPE_STANDARD) + ->with('client', 'account') + ->where('is_recurring', '=', false) + ->first(); if ($invoice) { $invoice->hidePrivateFields(); @@ -824,18 +825,27 @@ class AccountController extends BaseController } } + + (bool) $fireUpdateSubdomainEvent = false; + if ($account->subdomain !== $request->subdomain) { - event(new SubdomainWasUpdated($account)); + $fireUpdateSubdomainEvent = true; + event(new SubdomainWasRemoved($account)); } $account->fill($request->all()); $account->client_view_css = $request->client_view_css; - $account->subdomain = $request->subdomain; + $account->subdomain = $request->subdomain; $account->iframe_url = $request->iframe_url; $account->save(); + if ($fireUpdateSubdomainEvent) { + event(new SubdomainWasUpdated($account)); + } + + return redirect('settings/' . ACCOUNT_CLIENT_PORTAL) - ->with('message', trans('texts.updated_settings')); + ->with('message', trans('texts.updated_settings')); } /** @@ -852,7 +862,7 @@ class AccountController extends BaseController $settings->save(); return redirect('settings/' . ACCOUNT_EMAIL_SETTINGS) - ->with('message', trans('texts.updated_settings')); + ->with('message', trans('texts.updated_settings')); } /** @@ -1007,8 +1017,8 @@ class AccountController extends BaseController } if (! $account->share_counter - && $account->invoice_number_prefix == $account->quote_number_prefix - && $account->invoice_number_pattern == $account->quote_number_pattern) { + && $account->invoice_number_prefix == $account->quote_number_prefix + && $account->invoice_number_pattern == $account->quote_number_pattern) { Session::flash('error', trans('texts.invalid_counter')); return Redirect::to('settings/'.ACCOUNT_INVOICE_SETTINGS)->withInput(); @@ -1311,8 +1321,8 @@ class AccountController extends BaseController } $email = User::withTrashed()->where('email', '=', $email) - ->where('id', '<>', $user->registered ? 0 : $user->id) - ->first(); + ->where('id', '<>', $user->registered ? 0 : $user->id) + ->first(); if ($email) { return 'taken'; @@ -1520,8 +1530,8 @@ class AccountController extends BaseController { $template = Input::get('template'); $invitation = \App\Models\Invitation::scope() - ->with('invoice.client.contacts') - ->first(); + ->with('invoice.client.contacts') + ->first(); if (! $invitation) { return trans('texts.create_invoice_for_sample'); diff --git a/app/Listeners/DNSListener.php b/app/Listeners/DNSListener.php index 83d6253a74a4..33126f86520a 100644 --- a/app/Listeners/DNSListener.php +++ b/app/Listeners/DNSListener.php @@ -2,6 +2,7 @@ namespace App\Listeners; +use App\Events\SubdomainWasRemoved; use App\Events\SubdomainWasUpdated; use App\Ninja\DNS\Cloudflare; @@ -19,4 +20,11 @@ class DNSListener if(env("CLOUDFLARE_DNS_ENABLED")) Cloudflare::addDNSRecord($event->account); } + + public function removeDNSRecord(SubdomainWasRemoved $event) + { + if(env("CLOUDFLARE_DNS_ENABLED")) + Cloudflare::removeDNSRecord($event->account); + } + } diff --git a/app/Ninja/DNS/Cloudflare.php b/app/Ninja/DNS/Cloudflare.php index 5bdabf9e5912..3c6f31327399 100644 --- a/app/Ninja/DNS/Cloudflare.php +++ b/app/Ninja/DNS/Cloudflare.php @@ -18,31 +18,16 @@ class Cloudflare if($account->subdomain != "") { - $curl = curl_init(); $jsonEncodedData = json_encode(['type' => 'A', 'name' => $account->subdomain, 'content' => env('CLOUDFLARE_TARGET_IP_ADDRESS', ''), 'proxied' => true]); - $opts = [ - CURLOPT_URL => 'https://api.cloudflare.com/client/v4/zones/' . $zone . '/dns_records', - CURLOPT_RETURNTRANSFER => true, - CURLOPT_CUSTOMREQUEST => 'POST', - CURLOPT_POST => 1, - CURLOPT_POSTFIELDS => $jsonEncodedData, - CURLOPT_HTTPHEADER => ['Content-Type: application/json', - 'Content-Length: ' . strlen($jsonEncodedData), - 'X-Auth-Email: ' . env('CLOUDFLARE_EMAIL', ''), - 'X-Auth-Key: ' . env('CLOUDFLARE_API_KEY', '') - ], - ]; + $requestType = 'POST'; - curl_setopt_array($curl, $opts); + $url = 'https://api.cloudflare.com/client/v4/zones/' . $zone . '/dns_records'; - $result = curl_exec($curl); - $status = curl_getinfo($curl, CURLINFO_HTTP_CODE); + $response = self::curlCloudFlare($requestType, $url, $jsonEncodedData); - curl_close($curl); - - if ($status != 200) - Utils::logError('unable to update subdomain ' . $account->subdomain . ' @ Cloudflare - ' . $result); + if ($response['status'] != 200) + Utils::logError('Unable to update subdomain ' . $account->subdomain . ' @ Cloudflare - ' . $response['result']['result']); } @@ -51,5 +36,96 @@ class Cloudflare } + public static function removeDNSRecord(Account $account) { + + $zones = json_decode(env('CLOUDFLARE_ZONE_IDS',''), true); + + foreach($zones as $zone) + { + + if($account->subdomain != "") + { + + $dnsRecordId = self::getDNSRecord($zone, $account->subdomain); + + $jsonEncodedData = json_encode([]); + + $requestType = 'DELETE'; + + $url = 'https://api.cloudflare.com/client/v4/zones/' . $zone . '/dns_records/'. $dnsRecordId .''; + + $response = self::curlCloudFlare($requestType, $url, $jsonEncodedData); + + if ($response['status'] != 200) + Utils::logError('Unable to delete subdomain ' . $account->subdomain . ' @ Cloudflare - ' . $response['result']['result']); + + } + + } + + } + + public static function getDNSRecord($zone, $aRecord) + { + //harvest the zone_name + $url = 'https://api.cloudflare.com/client/v4/zones/'. $zone .'/dns_records?type=A&per_page=1'; + + $requestType = 'GET'; + + $jsonEncodedData = json_encode([]); + + $response = self::curlCloudFlare($requestType, $url, $jsonEncodedData); + + if ($response['status'] != 200) + Utils::logError('Unable to get the zone name for ' . $aRecord . ' @ Cloudflare - ' . $response['result']['result']); + + $zoneName = $response['result']['result'][0]['zone_name']; + + //get the A record + $url = 'https://api.cloudflare.com/client/v4/zones/'. $zone .'/dns_records?type=A&name='. $aRecord .'.'. $zoneName .' '; + + $response = self::curlCloudFlare($requestType, $url, $jsonEncodedData); + + if ($response['status'] != 200) + Utils::logError('Unable to get the record ID for ' . $aRecord . ' @ Cloudflare - ' . $response['result']['result']); + + return $response['result']['result'][0]['id']; + + } + + private static function curlCloudFlare($requestType, $url, $jsonEncodedData) + { + + $curl = curl_init(); + + $opts = [ + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_CUSTOMREQUEST => $requestType, + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => $jsonEncodedData, + CURLOPT_HTTPHEADER => ['Content-Type: application/json', + 'Content-Length: ' . strlen($jsonEncodedData), + 'X-Auth-Email: ' . env('CLOUDFLARE_EMAIL', ''), + 'X-Auth-Key: ' . env('CLOUDFLARE_API_KEY', '') + ], + ]; + + curl_setopt_array($curl, $opts); + + $result = curl_exec($curl); + + $status = curl_getinfo($curl, CURLINFO_HTTP_CODE); + + $data['status'] = $status; + + $data['result'] = \json_decode($result, true); + + curl_close($curl); + + return $data; + + } + } \ No newline at end of file diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 3f9723c93358..5a501e891df9 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -225,9 +225,14 @@ class EventServiceProvider extends ServiceProvider 'App\Listeners\InvoiceListener@jobFailed' ], - //DNS + //DNS Add A record to Cloudflare 'App\Events\SubdomainWasUpdated' => [ 'App\Listeners\DNSListener@addDNSRecord' + ], + + //DNS Remove A record from Cloudflare + 'App\Events\SubdomainWasRemoved' => [ + 'App\Listeners\DNSListener@removeDNSRecord' ] /*