Skip to content

Commit

Permalink
Merge pull request laravel#307 from eurides-eu/virtual-mail-mailgun
Browse files Browse the repository at this point in the history
setup virtual emailing for mailgun
  • Loading branch information
joelharkes authored Oct 25, 2018
2 parents 7f383b7 + 9218a95 commit aa37c68
Show file tree
Hide file tree
Showing 12 changed files with 347 additions and 103 deletions.
7 changes: 6 additions & 1 deletion .env.testing
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@ REDIS_PORT=6379

MAIL_DRIVER=log
PASSPORT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\\n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\\n-----END RSA PRIVATE KEY-----"
PASSPORT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1CeSlt55tyclsR6zSEqg\\nW5CkMFcGjyge/c5iUhkHr8IfaCZ1NkrHywsD700WiO9DpEZlDqapZDRrR9NGjiOc\\n2tOyB1ufZnG2ZmdpCsSph3my1G6cNrU/ZBrfvNxsmdlUG212qDNPyOZB8tTCUmzf\\nS5z4HMmiNFb6LZwBpHGCyOZUOi69HpOpFGJq7KAtVNV2KgndeIusFtd618CJG6/q\\nZ+lYFj2qbksIzuI6UCi5HYUglH0Sv8JaiJMDINHs44uIeTxv1W5vJ4vJTkaGRB4k\\n15m3GSl4mCBg1+3QUQGt9I3JbLTsZII+UBEwsyOzv+PVZbR3g+HHp8x7xx091txT\\nLMPcOmXduujacC9dMrSiM6a8M8q4KxGyhj+vOlQKYsGlENtw5OXhs7id1ovPS7No\\nq818BFwRhRemfVlO7it/+CPgTdRJm5f5WTd67+owDKKxPSkNuscLeo+HA6ZPzhxn\\naj7NkCyq+WaBOU/nmyJUbO0nAsKwlT1P1itR5DsPALLzCTxVMW6+54P6Kr4QYn1d\\n89ZnJ3/q6CpIeV9Csly47WqylvvVK1UlxrihFY78o01ESUt8d3/0XWZ9OK4nDaQm\\n0EJa4x4Np/GonAMpwfEBuXCcRpznnhPOf2kb9qbkudCmOcsQXQ9s8MwrPyVhOaLM\\nYaKfhy60Aynoc1lmto1rDDsCAwEAAQ==\\n-----END PUBLIC KEY-----"
PASSPORT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1CeSlt55tyclsR6zSEqg\\nW5CkMFcGjyge/c5iUhkHr8IfaCZ1NkrHywsD700WiO9DpEZlDqapZDRrR9NGjiOc\\n2tOyB1ufZnG2ZmdpCsSph3my1G6cNrU/ZBrfvNxsmdlUG212qDNPyOZB8tTCUmzf\\nS5z4HMmiNFb6LZwBpHGCyOZUOi69HpOpFGJq7KAtVNV2KgndeIusFtd618CJG6/q\\nZ+lYFj2qbksIzuI6UCi5HYUglH0Sv8JaiJMDINHs44uIeTxv1W5vJ4vJTkaGRB4k\\n15m3GSl4mCBg1+3QUQGt9I3JbLTsZII+UBEwsyOzv+PVZbR3g+HHp8x7xx091txT\\nLMPcOmXduujacC9dMrSiM6a8M8q4KxGyhj+vOlQKYsGlENtw5OXhs7id1ovPS7No\\nq818BFwRhRemfVlO7it/+CPgTdRJm5f5WTd67+owDKKxPSkNuscLeo+HA6ZPzhxn\\naj7NkCyq+WaBOU/nmyJUbO0nAsKwlT1P1itR5DsPALLzCTxVMW6+54P6Kr4QYn1d\\n89ZnJ3/q6CpIeV9Csly47WqylvvVK1UlxrihFY78o01ESUt8d3/0XWZ9OK4nDaQm\\n0EJa4x4Np/GonAMpwfEBuXCcRpznnhPOf2kb9qbkudCmOcsQXQ9s8MwrPyVhOaLM\\nYaKfhy60Aynoc1lmto1rDDsCAwEAAQ==\\n-----END PUBLIC KEY-----"


# For Testing unit tests virtual email
#VIRTUAL_MAIL_SERVICE=mailgun
#MAILGUN_APIKEY=
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ yarn-error.log
.env
.php_cs.cache
/.docker-cache/
/.docker-cache-maria/
.phpstorm.meta.php
4 changes: 3 additions & 1 deletion app/Http/Controllers/UserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ public function getVirtualMailExists(VirtualEmailRequest $request, VirtualMailIn
* @param VirtualEmailRequest $request
* @param VirtualMailInterface $virtualMailProvider
*
* @throws \Throwable
*
* @return \Psr\Http\Message\ResponseInterface
*/
public function setVirtualMail(VirtualEmailRequest $request, VirtualMailInterface $virtualMailProvider)
Expand All @@ -160,7 +162,7 @@ public function setVirtualMail(VirtualEmailRequest $request, VirtualMailInterfac
if ($emailFound) {
throw new BadRequestHttpException('Virtual email already in use.');
}
$newVirtualMail = $virtualMailProvider->createMail($request->getVirtualEmail(), $user->email);
$newVirtualMail = $virtualMailProvider->saveMail($request->getVirtualEmail(), $user->email);
$user->virtual_email = $newVirtualMail;
$user->save();

Expand Down
50 changes: 50 additions & 0 deletions app/Lib/JsonHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace App\Lib;

class JsonHelper
{
/**
* Json string to php array.
* Throws if fails to decode json :).
*
* @param $jsonString
*
* @throws \Exception when decoding fails
*
* @return mixed
*/
public static function decode($jsonString)
{
// todo this code can be simplified in php7.3
$data = json_decode($jsonString, true);
if (null === $data && JSON_ERROR_NONE !== json_last_error()) {
$err = json_last_error_msg();
throw new \Exception("failed to parse to json: $err, $jsonString");
}

return $data;
}

/**
* Php Array to Json string.
* Throws if fails to decode json :).
*
* @param $data
*
* @throws \Exception
*
* @return mixed
*/
public static function encode($data): string
{
// todo this code can be simplified in php7.3
$data = json_encode($data);
if (false === $data && JSON_ERROR_NONE !== json_last_error()) {
$err = json_last_error_msg();
throw new \Exception("failed to decode to json: $err");
}

return $data;
}
}
43 changes: 15 additions & 28 deletions app/Lib/VirtualMail/EmptyVirtualMail.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,51 +10,38 @@ class EmptyVirtualMail implements VirtualMailInterface
/** @var string */
private $domain;

public function __construct(string $domain)
public function __construct()
{
$this->domain = $domain;
$this->domain = config('virtualmail.domain');
}

/**
* Create virtual forward mail (alias).
* Delete mail alias.
*
* @param string $mail email alias to use (this is the part before @)
* @param string $forwardTo email adress to forward to
* @param string $mail
*
* @return string
* @throws \Throwable if delete fails.
*/
public function createMail(string $mail, string $forwardTo): string
public function deleteMail(string $mail)
{
return "$mail@$this->domain";
}

/**
* Update destination of mail alias.
*
* @param string $mail
* @param string $forwardTo
*
* @throws \Throwable if update fails.
*
* @return string
*/
public function updateMail(string $mail, string $forwardTo): string
public function aliasToMailAddress(string $mailAlias): string
{
return "$mail@$this->domain";
return "$mailAlias@$this->domain";
}

/**
* Delete mail alias.
* Ensures virtual mail forwarding route is created/updated to $forwardTo address.
*
* @param string $mail
* @param string $mailAlias
* @param string $forwardTo
*
* @throws \Throwable if delete fails.
* @throws \Throwable if update fails.
*
* @return string full virtual email including domain
*/
public function deleteMail(string $mail)
{
}

public function aliasToMailAddress(string $mailAlias): string
public function saveMail(string $mailAlias, string $forwardTo): string
{
return "$mailAlias@$this->domain";
}
Expand Down
114 changes: 67 additions & 47 deletions app/Lib/VirtualMail/ForwardMxVirtualMail.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,64 +26,58 @@ class ForwardMxVirtualMail implements VirtualMailInterface
public function __construct()
{
$this->forwardMails = (string) config('virtualmail.forwardMails');
$this->apikey = (string) config('virtualmail.apikey');
$this->apikey = (string) config('virtualmail.forwardMX.apikey');
$this->domain = (string) config('virtualmail.domain');
}

protected function getClient(): Client
{
return new Client([
'base_uri' => config('virtualmail.host'),
'base_uri' => config('virtualmail.forwardMX.host') . '/',
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
],
]);
}

public function aliasToMailAddress(string $mailAlias): string
{
return "$mailAlias@$this->domain";
}

/**
* @param string $url
* @param array $json
* Ensures virtual mail forwarding route is created/updated to $forwardTo address.
*
* @param string $mailAlias
* @param string $forwardTo
*
* @throws \Throwable if update fails.
* @throws \GuzzleHttp\Exception\GuzzleException
* @throws \Exception
*
* @return mixed|array
* @return string full virtual email including domain
*/
protected function callApi(string $url, array $json)
public function saveMail(string $mailAlias, string $forwardTo): string
{
$json['domain'] = $this->domain;
$json['key'] = $this->apikey;

$response = $this->getClient()->request('POST', $url, ['json' => $json]);

$httpStatusCode = $response->getStatusCode();
if ($httpStatusCode >= 300 || $httpStatusCode < 200) {
// they always send status code 200 so there must be something weird here!
throw new ServiceUnavailableHttpException(null, 'Service unavailable (ForwardMX)', null, ['X-message' => $response->getBody()], $httpStatusCode);
}

$data = json_decode($response->getBody()->getContents(), true);
if (array_key_exists('error', $data) && 'Domain not found' === $data['error']) {
throw new \Exception("Domain $this->domain has not been configured by ForwardMX.");
try {
return $this->createMail($mailAlias, $forwardTo);
} catch (\Throwable $exception) {
return $this->updateMail($mailAlias, $forwardTo);
}

return $data;
}

/**
* @param array $responseJson
* @param string $mail
*
* @throws \GuzzleHttp\Exception\GuzzleException
* @throws \Exception
*/
private function assertSuccess($responseJson)
public function deleteMail(string $mail)
{
if (!is_array($responseJson)) {
throw new \Exception('Forward MX api result did not return json object as expected');
}
if (array_key_exists('error', $responseJson)) {
throw new \Exception("Forward MX error: {$responseJson['error']}");
}
$response = $this->callApi('alias/destroy', [
'alias' => $mail,
]);
$this->assertSuccess($response);
}

/**
Expand All @@ -95,9 +89,9 @@ private function assertSuccess($responseJson)
*
* @return string
*/
public function createMail(string $mail, string $forwardTo): string
private function createMail(string $mail, string $forwardTo): string
{
$response = $this->callApi('/api/alias/create', [
$response = $this->callApi('alias/create', [
'alias' => $mail,
'destination' => $forwardTo . ',' . $this->forwardMails,
]);
Expand All @@ -109,40 +103,66 @@ public function createMail(string $mail, string $forwardTo): string

/**
* @param string $mail
* @param string $forwardTo
*
* @throws \GuzzleHttp\Exception\GuzzleException
* @throws \Exception
*
* @return string
*/
public function deleteMail(string $mail)
private function updateMail(string $mail, string $forwardTo): string
{
$response = $this->callApi('/api/alias/destroy', [
$response = $this->callApi('alias/update', [
'alias' => $mail,
'destination' => $forwardTo . ',' . $this->forwardMails,
]);
$this->assertSuccess($response);

return "$mail@$this->domain";
}

/**
* @param string $mail
* @param string $forwardTo
* @param string $url
* @param array $json
*
* @throws \GuzzleHttp\Exception\GuzzleException
* @throws \Exception
*
* @return string
* @return mixed|array
*/
public function updateMail(string $mail, string $forwardTo): string
protected function callApi(string $url, array $json)
{
$response = $this->callApi('/api/alias/update', [
'alias' => $mail,
'destination' => $forwardTo . ',' . $this->forwardMails,
]);
$this->assertSuccess($response);
$json['domain'] = $this->domain;
$json['key'] = $this->apikey;

return "$mail@$this->domain";
$response = $this->getClient()->request('POST', $url, ['json' => $json]);

$httpStatusCode = $response->getStatusCode();
if ($httpStatusCode >= 300 || $httpStatusCode < 200) {
// they always send status code 200 so there must be something weird here!
throw new ServiceUnavailableHttpException(null, 'Service unavailable (ForwardMX)', null, ['X-message' => $response->getBody()], $httpStatusCode);
}

$data = json_decode($response->getBody()->getContents(), true);
if (array_key_exists('error', $data) && 'Domain not found' === $data['error']) {
throw new \Exception("Domain $this->domain has not been configured by ForwardMX.");
}

return $data;
}

public function aliasToMailAddress(string $mailAlias): string
/**
* @param array $responseJson
*
* @throws \Exception
*/
private function assertSuccess($responseJson)
{
return "$mailAlias@$this->domain";
if (!is_array($responseJson)) {
throw new \Exception('Forward MX api result did not return json object as expected');
}
if (array_key_exists('error', $responseJson)) {
throw new \Exception("Forward MX error: {$responseJson['error']}");
}
}
}
Loading

0 comments on commit aa37c68

Please sign in to comment.