From 73aef1b9a45595aa5ca1959beeeb088346d06b72 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 26 May 2018 13:55:11 +0200 Subject: [PATCH] Code for #1415 --- app/Handlers/Events/APIEventHandler.php | 80 +++++++++++++++++++ app/Mail/AccessTokenCreatedMail.php | 68 ++++++++++++++++ app/Mail/ConfirmEmailChangeMail.php | 2 +- app/Mail/OAuthTokenCreatedMail.php | 70 ++++++++++++++++ app/Mail/UndoEmailChangeMail.php | 2 +- app/Providers/EventServiceProvider.php | 53 +++++++++--- app/User.php | 1 + .../emails/access-token-created-html.twig | 13 +++ .../emails/access-token-created-text.twig | 7 ++ .../emails/oauth-client-created-html.twig | 14 ++++ .../emails/oauth-client-created-text.twig | 9 +++ 11 files changed, 308 insertions(+), 11 deletions(-) create mode 100644 app/Handlers/Events/APIEventHandler.php create mode 100644 app/Mail/AccessTokenCreatedMail.php create mode 100644 app/Mail/OAuthTokenCreatedMail.php create mode 100644 resources/views/emails/access-token-created-html.twig create mode 100644 resources/views/emails/access-token-created-text.twig create mode 100644 resources/views/emails/oauth-client-created-html.twig create mode 100644 resources/views/emails/oauth-client-created-text.twig diff --git a/app/Handlers/Events/APIEventHandler.php b/app/Handlers/Events/APIEventHandler.php new file mode 100644 index 0000000000..64aa286420 --- /dev/null +++ b/app/Handlers/Events/APIEventHandler.php @@ -0,0 +1,80 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Handlers\Events; + + +use Exception; +use FireflyIII\Mail\AccessTokenCreatedMail; +use FireflyIII\Repositories\User\UserRepositoryInterface; +use Laravel\Passport\Events\AccessTokenCreated; +use Laravel\Passport\Token; +use Log; +use Mail; +use Request; +use Session; + +/** + * Class APIEventHandler + */ +class APIEventHandler +{ + /** + * @param AccessTokenCreated $event + * + * @return bool + */ + public function accessTokenCreated(AccessTokenCreated $event): bool + { + /** @var UserRepositoryInterface $repository */ + $repository = app(UserRepositoryInterface::class); + $user = $repository->findNull((int)$event->userId); + if (null === $user) { + Log::error('Access Token generated but no user associated.'); + + return true; + } + + $email = $user->email; + $ipAddress = Request::ip(); + + Log::debug(sprintf('Now in APIEventHandler::accessTokenCreated. Email is %s, IP is %s', $email, $ipAddress)); + try { + Log::debug('Trying to send message...'); + Mail::to($email)->send(new AccessTokenCreatedMail($email, $ipAddress)); + // @codeCoverageIgnoreStart + } catch (Exception $e) { + Log::debug('Send message failed! :('); + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + Session::flash('error', 'Possible email error: ' . $e->getMessage()); + } + Log::debug('If no error above this line, message was sent.'); + + // @codeCoverageIgnoreEnd + return true; + + + } + +} \ No newline at end of file diff --git a/app/Mail/AccessTokenCreatedMail.php b/app/Mail/AccessTokenCreatedMail.php new file mode 100644 index 0000000000..6e0e43e8a4 --- /dev/null +++ b/app/Mail/AccessTokenCreatedMail.php @@ -0,0 +1,68 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Mail; + + +use Illuminate\Bus\Queueable; +use Illuminate\Mail\Mailable; +use Illuminate\Queue\SerializesModels; +use Laravel\Passport\Token; + +/** + * Class AccessTokenCreatedMail + */ +class AccessTokenCreatedMail extends Mailable +{ + + use Queueable, SerializesModels; + + /** @var string Email address of admin */ + public $email; + /** @var string IP address of admin */ + public $ipAddress; + + /** + * AccessTokenCreatedMail constructor. + * + * @param string $email + * @param string $ipAddress + * @param Token $token + */ + public function __construct(string $email, string $ipAddress) + { + $this->email = $email; + $this->ipAddress = $ipAddress; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + return $this->view('emails.access-token-created-html')->text('emails.access-token-created-text') + ->subject('A new access token was created'); + } +} \ No newline at end of file diff --git a/app/Mail/ConfirmEmailChangeMail.php b/app/Mail/ConfirmEmailChangeMail.php index 4a0e5dd1cd..9289671ea9 100644 --- a/app/Mail/ConfirmEmailChangeMail.php +++ b/app/Mail/ConfirmEmailChangeMail.php @@ -68,6 +68,6 @@ class ConfirmEmailChangeMail extends Mailable public function build() { return $this->view('emails.confirm-email-change-html')->text('emails.confirm-email-change-text') - ->subject('Your Firefly III email address has changed.'); + ->subject('Your Firefly III email address has changed'); } } diff --git a/app/Mail/OAuthTokenCreatedMail.php b/app/Mail/OAuthTokenCreatedMail.php new file mode 100644 index 0000000000..5036c1225d --- /dev/null +++ b/app/Mail/OAuthTokenCreatedMail.php @@ -0,0 +1,70 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Mail; + +use Illuminate\Bus\Queueable; +use Illuminate\Mail\Mailable; +use Illuminate\Queue\SerializesModels; +use Laravel\Passport\Client; + + +/** + * Class OAuthTokenCreatedMail + */ +class OAuthTokenCreatedMail extends Mailable +{ + use Queueable, SerializesModels; + + /** @var Client The client */ + public $client; + /** @var string Email address of admin */ + public $email; + /** @var string IP address of admin */ + public $ipAddress; + + /** + * OAuthTokenCreatedMail constructor. + * + * @param string $email + * @param string $ipAddress + * @param Client $client + */ + public function __construct(string $email, string $ipAddress, Client $client) + { + $this->email = $email; + $this->ipAddress = $ipAddress; + $this->client = $client; + } + + /** + * Build the message. + * + * @return $this + */ + public function build(): self + { + return $this->view('emails.oauth-client-created-html')->text('emails.oauth-client-created-text') + ->subject('A new OAuth client has been created'); + } +} \ No newline at end of file diff --git a/app/Mail/UndoEmailChangeMail.php b/app/Mail/UndoEmailChangeMail.php index 94a6b5b5ab..a47d6ee9f4 100644 --- a/app/Mail/UndoEmailChangeMail.php +++ b/app/Mail/UndoEmailChangeMail.php @@ -66,6 +66,6 @@ class UndoEmailChangeMail extends Mailable public function build() { return $this->view('emails.undo-email-change-html')->text('emails.undo-email-change-text') - ->subject('Your Firefly III email address has changed.'); + ->subject('Your Firefly III email address has changed'); } } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 75b5dc0921..352e357d22 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace FireflyIII\Providers; +use Exception; use FireflyIII\Events\AdminRequestedTestMessage; use FireflyIII\Events\RegisteredUser; use FireflyIII\Events\RequestedNewPassword; @@ -29,10 +30,18 @@ use FireflyIII\Events\RequestedVersionCheckStatus; use FireflyIII\Events\StoredTransactionJournal; use FireflyIII\Events\UpdatedTransactionJournal; use FireflyIII\Events\UserChangedEmail; +use FireflyIII\Mail\OAuthTokenCreatedMail; use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBankRepetition; +use FireflyIII\Repositories\User\UserRepositoryInterface; use Illuminate\Auth\Events\Login; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; +use Laravel\Passport\Client; +use Laravel\Passport\Events\AccessTokenCreated; +use Log; +use Mail; +use Request; +use Session; /** * Class EventServiceProvider. @@ -82,6 +91,10 @@ class EventServiceProvider extends ServiceProvider UpdatedTransactionJournal::class => [ 'FireflyIII\Handlers\Events\UpdatedJournalEventHandler@processRules', ], + // API related events: + AccessTokenCreated::class => [ + 'FireflyIII\Handlers\Events\APIEventHandler@accessTokenCreated', + ], ]; /** @@ -91,14 +104,13 @@ class EventServiceProvider extends ServiceProvider public function boot() { parent::boot(); - $this->registerDeleteEvents(); $this->registerCreateEvents(); } /** * */ - protected function registerCreateEvents() + protected function registerCreateEvents(): void { // move this routine to a filter // in case of repeated piggy banks and/or other problems. @@ -112,13 +124,36 @@ class EventServiceProvider extends ServiceProvider $repetition->save(); } ); + Client::created( + function (Client $oauthClient) { + /** @var UserRepositoryInterface $repository */ + $repository = app(UserRepositoryInterface::class); + $user = $repository->findNull((int)$oauthClient->user_id); + if (null === $user) { + Log::error('OAuth client generated but no user associated.'); + + return; + } + + $email = $user->email; + $ipAddress = Request::ip(); + + Log::debug(sprintf('Now in EventServiceProvider::registerCreateEvents. Email is %s, IP is %s', $email, $ipAddress)); + try { + Log::debug('Trying to send message...'); + Mail::to($email)->send(new OAuthTokenCreatedMail($email, $ipAddress, $oauthClient)); + // @codeCoverageIgnoreStart + } catch (Exception $e) { + Log::debug('Send message failed! :('); + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + Session::flash('error', 'Possible email error: ' . $e->getMessage()); + } + Log::debug('If no error above this line, message was sent.'); + + + } + ); } - /** - * - */ - protected function registerDeleteEvents() - { - - } } diff --git a/app/User.php b/app/User.php index b52ef745cb..3e3150fd13 100644 --- a/app/User.php +++ b/app/User.php @@ -56,6 +56,7 @@ use FireflyIII\Models\Attachment; /** * Class User. * @property int $id + * @property string $email */ class User extends Authenticatable { diff --git a/resources/views/emails/access-token-created-html.twig b/resources/views/emails/access-token-created-html.twig new file mode 100644 index 0000000000..1c88099a1d --- /dev/null +++ b/resources/views/emails/access-token-created-html.twig @@ -0,0 +1,13 @@ +{% include 'emails.header-html' %} +

+ Somebody (hopefully you) just created a new Firefly III API Access Token for your user account. +

+ +

+ With this token, they can access all of your financial records through the Firefly III API. +

+ +

+ If this wasn't you, please revoke this token as soon as possible at {{ route('profile.index') }}. +

+{% include 'emails.footer-html' %} diff --git a/resources/views/emails/access-token-created-text.twig b/resources/views/emails/access-token-created-text.twig new file mode 100644 index 0000000000..5ea41eb0fe --- /dev/null +++ b/resources/views/emails/access-token-created-text.twig @@ -0,0 +1,7 @@ +{% include 'emails.header-text' %} +Somebody (hopefully you) just created a new Firefly III API Access Token for your user account. + +With this token, they can access all of your financial records through the Firefly III API. + +If this wasn't you, please revoke this token as soon as possible at {{ route('profile.index') }}. +{% include 'emails.footer-text' %} diff --git a/resources/views/emails/oauth-client-created-html.twig b/resources/views/emails/oauth-client-created-html.twig new file mode 100644 index 0000000000..f49cd54b34 --- /dev/null +++ b/resources/views/emails/oauth-client-created-html.twig @@ -0,0 +1,14 @@ +{% include 'emails.header-html' %} +

+ Somebody (hopefully you) just created a new Firefly III API OAuth Client for your user account. It's labeled "{{ client.name }}" + and has callback URL {{ client.redirect }}. +

+ +

+ With this client, they can access all of your financial records through the Firefly III API. +

+ +

+ If this wasn't you, please revoke this client as soon as possible at {{ route('profile.index') }}. +

+{% include 'emails.footer-html' %} diff --git a/resources/views/emails/oauth-client-created-text.twig b/resources/views/emails/oauth-client-created-text.twig new file mode 100644 index 0000000000..d74222d0f4 --- /dev/null +++ b/resources/views/emails/oauth-client-created-text.twig @@ -0,0 +1,9 @@ +{% include 'emails.header-text' %} +Somebody (hopefully you) just created a new Firefly III API OAuth Client for your user account. It's labeled "{{ client.name }}" and has callback URL: + +{{ client.redirect }} + +With this client, they can access all of your financial records through the Firefly III API. + +If this wasn't you, please revoke this client as soon as possible at {{ route('profile.index') }}. +{% include 'emails.footer-text' %}