From a9e57e1c34f703f5c1b8a9cc7c39505b1b17be34 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 13 Dec 2016 17:21:28 +0100 Subject: [PATCH] First set of code for #461 --- app/Events/BlockedBadLogin.php | 41 +++++++ app/Events/BlockedUserLogin.php | 42 +++++++ app/Events/LockedOutUser.php | 41 +++++++ app/Handlers/Events/UserEventHandler.php | 106 ++++++++++++++++++ .../Admin/ConfigurationController.php | 18 ++- app/Http/Controllers/Auth/LoginController.php | 46 ++------ app/Http/Requests/ConfigurationRequest.php | 22 +++- app/Providers/EventServiceProvider.php | 13 +++ config/firefly.php | 11 +- .../views/admin/configuration/index.twig | 21 ++++ .../views/emails/blocked-bad-creds-html.twig | 5 + .../views/emails/blocked-bad-creds-text.twig | 3 + resources/views/emails/locked-out-html.twig | 5 + resources/views/emails/locked-out-text.twig | 3 + 14 files changed, 331 insertions(+), 46 deletions(-) create mode 100644 app/Events/BlockedBadLogin.php create mode 100644 app/Events/BlockedUserLogin.php create mode 100644 app/Events/LockedOutUser.php create mode 100644 resources/views/emails/blocked-bad-creds-html.twig create mode 100644 resources/views/emails/blocked-bad-creds-text.twig create mode 100644 resources/views/emails/locked-out-html.twig create mode 100644 resources/views/emails/locked-out-text.twig diff --git a/app/Events/BlockedBadLogin.php b/app/Events/BlockedBadLogin.php new file mode 100644 index 0000000000..fb984abd04 --- /dev/null +++ b/app/Events/BlockedBadLogin.php @@ -0,0 +1,41 @@ +email = $email; + $this->ipAddress = $ipAddress; + } +} diff --git a/app/Events/BlockedUserLogin.php b/app/Events/BlockedUserLogin.php new file mode 100644 index 0000000000..fd12fb8a36 --- /dev/null +++ b/app/Events/BlockedUserLogin.php @@ -0,0 +1,42 @@ +user = $user; + $this->ipAddress = $ipAddress; + } +} diff --git a/app/Events/LockedOutUser.php b/app/Events/LockedOutUser.php new file mode 100644 index 0000000000..c498ad1ed7 --- /dev/null +++ b/app/Events/LockedOutUser.php @@ -0,0 +1,41 @@ +email = $email; + $this->ipAddress = $ipAddress; + } +} diff --git a/app/Handlers/Events/UserEventHandler.php b/app/Handlers/Events/UserEventHandler.php index 7b1f655458..b414cfeaf3 100644 --- a/app/Handlers/Events/UserEventHandler.php +++ b/app/Handlers/Events/UserEventHandler.php @@ -15,8 +15,11 @@ namespace FireflyIII\Handlers\Events; use Exception; use FireflyConfig; +use FireflyIII\Events\BlockedBadLogin; +use FireflyIII\Events\BlockedUserLogin; use FireflyIII\Events\ConfirmedUser; use FireflyIII\Events\DeletedUser; +use FireflyIII\Events\LockedOutUser; use FireflyIII\Events\RegisteredUser; use FireflyIII\Events\RequestedNewPassword; use FireflyIII\Events\ResentConfirmation; @@ -77,6 +80,109 @@ class UserEventHandler return true; } + /** + * @param BlockedBadLogin $event + * + * @return bool + */ + public function respondToBlockedBadLogin(BlockedBadLogin $event) + { + $email = $event->email; + $ipAddress = $event->ipAddress; + /** @var Configuration $sendmail */ + $sendmail = FireflyConfig::get('mail_for_bad_login', config('firefly.configuration.mail_for_bad_login')); + Log::debug(sprintf('Now in respondToBlockedBadLogin for email address %s', $email)); + if (is_null($sendmail) || (!is_null($sendmail) && $sendmail->data === false)) { + Log::error(sprintf('User %s tried to login with bad credentials.', $email)); + + return true; + } + + // send email message: + try { + Mail::send( + ['emails.blocked-bad-creds-html', 'emails.blocked-bad-creds-text'], ['email' => $email, 'ip' => $ipAddress], function (Message $message) use ($email) { + $message->to($email, $email)->subject('Blocked login attempt with bad credentials'); + } + ); + } catch (Swift_TransportException $e) { + Log::error($e->getMessage()); + } + + return true; + } + + /** + * @param BlockedUserLogin $event + * + * @return bool + */ + public function respondToBlockedUserLogin(BlockedUserLogin $event): bool + { + $user = $event->user; + $email = $user->email; + $ipAddress = $event->ipAddress; + /** @var Configuration $sendmail */ + $sendmail = FireflyConfig::get('mail_for_blocked_login', config('firefly.configuration.mail_for_blocked_login')); + Log::debug(sprintf('Now in respondToBlockedUserLogin for email address %s', $email)); + if (is_null($sendmail) || (!is_null($sendmail) && $sendmail->data === false)) { + Log::error(sprintf('User #%d (%s) has their accout blocked (blocked_code is "%s") but tried to login.', $user->id, $email, $user->blocked_code)); + + return true; + } + + // send email message: + try { + Mail::send( + ['emails.blocked-login-html', 'emails.blocked-login-text'], + [ + 'user_id' => $user->id, + 'user_address' => $email, + 'ip' => $ipAddress, + 'code' => $user->blocked_code, + ], function (Message $message) use ($email, $user) { + $message->to($email, $email)->subject('Blocked login attempt of blocked user'); + } + ); + } catch (Swift_TransportException $e) { + Log::error($e->getMessage()); + } + + return true; + } + + /** + * @param LockedOutUser $event + * + * @return bool + */ + public function respondToLockout(LockedOutUser $event): bool + { + $email = $event->email; + $ipAddress = $event->ipAddress; + /** @var Configuration $sendmail */ + $sendmail = FireflyConfig::get('mail_for_lockout', config('firefly.configuration.mail_for_lockout')); + Log::debug(sprintf('Now in respondToLockout for email address %s', $email)); + if (is_null($sendmail) || (!is_null($sendmail) && $sendmail->data === false)) { + Log::error(sprintf('User %s was locked out after too many invalid login attempts.', $email)); + + return true; + } + + // send email message: + try { + Mail::send( + ['emails.locked-out-html', 'emails.locked-out-text'], ['email' => $email, 'ip' => $ipAddress], function (Message $message) use ($email) { + $message->to($email, $email)->subject('User was locked out'); + } + ); + } catch (Swift_TransportException $e) { + Log::error($e->getMessage()); + } + + return true; + } + /** * @param DeletedUser $event * diff --git a/app/Http/Controllers/Admin/ConfigurationController.php b/app/Http/Controllers/Admin/ConfigurationController.php index 62e5c1298a..4672c71476 100644 --- a/app/Http/Controllers/Admin/ConfigurationController.php +++ b/app/Http/Controllers/Admin/ConfigurationController.php @@ -62,7 +62,16 @@ class ConfigurationController extends Controller $mustConfirmAccount = FireflyConfig::get('must_confirm_account', config('firefly.configuration.must_confirm_account'))->data; $isDemoSite = FireflyConfig::get('is_demo_site', config('firefly.configuration.is_demo_site'))->data; - return view('admin.configuration.index', compact('subTitle', 'subTitleIcon', 'singleUserMode', 'mustConfirmAccount', 'isDemoSite')); + // email settings: + $sendErrorMessage = [ + 'mail_for_lockout' => FireflyConfig::get('mail_for_lockout', config('firefly.configuration.mail_for_lockout'))->data, + 'mail_for_blocked_domain' => FireflyConfig::get('mail_for_blocked_domain', config('firefly.configuration.mail_for_blocked_domain'))->data, + 'mail_for_blocked_email' => FireflyConfig::get('mail_for_blocked_email', config('firefly.configuration.mail_for_blocked_email'))->data, + 'mail_for_bad_login' => FireflyConfig::get('mail_for_bad_login', config('firefly.configuration.mail_for_bad_login'))->data, + 'mail_for_blocked_login' => FireflyConfig::get('mail_for_blocked_login', config('firefly.configuration.mail_for_blocked_login'))->data, + ]; + + return view('admin.configuration.index', compact('subTitle', 'subTitleIcon', 'singleUserMode', 'mustConfirmAccount', 'isDemoSite', 'sendErrorMessage')); } @@ -81,6 +90,13 @@ class ConfigurationController extends Controller FireflyConfig::set('must_confirm_account', $data['must_confirm_account']); FireflyConfig::set('is_demo_site', $data['is_demo_site']); + // email settings + FireflyConfig::set('mail_for_lockout', $data['mail_for_lockout']); + FireflyConfig::set('mail_for_blocked_domain', $data['mail_for_blocked_domain']); + FireflyConfig::set('mail_for_blocked_email', $data['mail_for_blocked_email']); + FireflyConfig::set('mail_for_bad_login', $data['mail_for_bad_login']); + FireflyConfig::set('mail_for_blocked_login', $data['mail_for_blocked_login']); + // flash message Session::flash('success', strval(trans('firefly.configuration_updated'))); Preferences::mark(); diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index afaa6b3b48..0e787b4fde 100755 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -14,15 +14,14 @@ namespace FireflyIII\Http\Controllers\Auth; use Config; use FireflyConfig; +use FireflyIII\Events\BlockedBadLogin; +use FireflyIII\Events\BlockedUserLogin; +use FireflyIII\Events\LockedOutUser; use FireflyIII\Http\Controllers\Controller; use FireflyIII\User; use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Http\Request; -use Illuminate\Mail\Message; use Lang; -use Log; -use Mail; -use Swift_TransportException; /** * Class LoginController @@ -75,6 +74,8 @@ class LoginController extends Controller if ($lockedOut) { $this->fireLockoutEvent($request); + event(new LockedOutUser($request->get('email'), $request->ip())); + return $this->sendLockoutResponse($request); } @@ -90,10 +91,13 @@ class LoginController extends Controller /** @var User $foundUser */ $foundUser = User::where('email', $credentials['email'])->where('blocked', 1)->first(); if (!is_null($foundUser)) { - // if it exists, show message: + // user exists, but is blocked: $code = strlen(strval($foundUser->blocked_code)) > 0 ? $foundUser->blocked_code : 'general_blocked'; $errorMessage = strval(trans('firefly.' . $code . '_error', ['email' => $credentials['email']])); - $this->reportBlockedUserLoginAttempt($foundUser, $code, $request->ip()); + event(new BlockedUserLogin($foundUser, $request->ip())); + } + if (is_null($foundUser)) { + event(new BlockedBadLogin($credentials['email'], $request->ip())); } // If the login attempt was unsuccessful we will increment the number of attempts @@ -163,34 +167,4 @@ class LoginController extends Controller ] ); } - - /** - * Send a message home about the blocked attempt to login. - * Perhaps in a later stage, simply log these messages. - * - * @param User $user - * @param string $code - * @param string $ipAddress - */ - private function reportBlockedUserLoginAttempt(User $user, string $code, string $ipAddress) - { - - try { - $email = env('SITE_OWNER', false); - $fields = [ - 'user_id' => $user->id, - 'user_address' => $user->email, - 'code' => $code, - 'ip' => $ipAddress, - ]; - - Mail::send( - ['emails.blocked-login-html', 'emails.blocked-login-text'], $fields, function (Message $message) use ($email, $user) { - $message->to($email, $email)->subject('Blocked a login attempt from ' . trim($user->email) . '.'); - } - ); - } catch (Swift_TransportException $e) { - Log::error($e->getMessage()); - } - } } diff --git a/app/Http/Requests/ConfigurationRequest.php b/app/Http/Requests/ConfigurationRequest.php index c2f2d3becd..1647835a3b 100644 --- a/app/Http/Requests/ConfigurationRequest.php +++ b/app/Http/Requests/ConfigurationRequest.php @@ -36,9 +36,14 @@ class ConfigurationRequest extends Request public function getConfigurationData(): array { return [ - 'single_user_mode' => intval($this->get('single_user_mode')) === 1, - 'must_confirm_account' => intval($this->get('must_confirm_account')) === 1, - 'is_demo_site' => intval($this->get('is_demo_site')) === 1, + 'single_user_mode' => intval($this->get('single_user_mode')) === 1, + 'must_confirm_account' => intval($this->get('must_confirm_account')) === 1, + 'is_demo_site' => intval($this->get('is_demo_site')) === 1, + 'mail_for_lockout' => intval($this->get('mail_for_lockout')) === 1, + 'mail_for_blocked_domain' => intval($this->get('mail_for_blocked_domain')) === 1, + 'mail_for_blocked_email' => intval($this->get('mail_for_blocked_email')) === 1, + 'mail_for_bad_login' => intval($this->get('mail_for_bad_login')) === 1, + 'mail_for_blocked_login' => intval($this->get('mail_for_blocked_login')) === 1, ]; } @@ -48,9 +53,14 @@ class ConfigurationRequest extends Request public function rules() { $rules = [ - 'single_user_mode' => 'between:0,1|numeric', - 'must_confirm_account' => 'between:0,1|numeric', - 'is_demo_site' => 'between:0,1|numeric', + 'single_user_mode' => 'between:0,1|numeric', + 'must_confirm_account' => 'between:0,1|numeric', + 'is_demo_site' => 'between:0,1|numeric', + 'mail_for_lockout' => 'between:0,1|numeric', + 'mail_for_blocked_domain' => 'between:0,1|numeric', + 'mail_for_blocked_email' => 'between:0,1|numeric', + 'mail_for_bad_login' => 'between:0,1|numeric', + 'mail_for_blocked_login' => 'between:0,1|numeric', ]; return $rules; diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 8af4b0348c..0b04eaf9af 100755 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -41,10 +41,23 @@ class EventServiceProvider extends ServiceProvider [ 'FireflyIII\Handlers\Events\UserEventHandler@storeConfirmationIpAddress', ], + 'FireflyIII\Events\DeletedUser' => // is a User related event. [ 'FireflyIII\Handlers\Events\UserEventHandler@saveEmailAddress', ], + 'FireflyIII\Events\LockedOutUser' => // is a User related event. + [ + 'FireflyIII\Handlers\Events\UserEventHandler@respondToLockout', + ], + 'FireflyIII\Events\BlockedUserLogin' => // is a User related event. + [ + 'FireflyIII\Handlers\Events\UserEventHandler@respondToBlockedUserLogin', + ], + 'FireflyIII\Events\BlockedBadLogin' => // is a User related event. + [ + 'FireflyIII\Handlers\Events\UserEventHandler@respondToBlockedBadLogin', + ], 'FireflyIII\Events\RegisteredUser' => // is a User related event. [ 'FireflyIII\Handlers\Events\UserEventHandler@sendRegistrationMail', diff --git a/config/firefly.php b/config/firefly.php index f9e2710a74..07fdd04ee1 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -19,9 +19,14 @@ declare(strict_types = 1); return [ 'configuration' => [ - 'single_user_mode' => true, - 'is_demo_site' => false, - 'must_confirm_account' => false, + 'single_user_mode' => true, + 'is_demo_site' => false, + 'must_confirm_account' => false, + 'mail_for_lockout' => false, + 'mail_for_blocked_domain' => false, + 'mail_for_blocked_email' => false, + 'mail_for_bad_login' => false, + 'mail_for_blocked_login' => false, ], 'chart' => 'chartjs', 'version' => '4.2.1', diff --git a/resources/views/admin/configuration/index.twig b/resources/views/admin/configuration/index.twig index e4d193315f..9174c6fbc9 100644 --- a/resources/views/admin/configuration/index.twig +++ b/resources/views/admin/configuration/index.twig @@ -53,6 +53,7 @@ + {# send email messages #} +
+ {# send email messages about stuff: #} +
+
+
+

{{ 'setting_send_email_notifications'|_ }}

+
+
+

+ {{ 'setting_send_email_explain'|_ }} +

+ {{ ExpandedForm.checkbox('mail_for_lockout','1', sendErrorMessage.mail_for_lockout) }} + {{ ExpandedForm.checkbox('mail_for_blocked_domain','1', sendErrorMessage.mail_for_blocked_domain) }} + {{ ExpandedForm.checkbox('mail_for_blocked_email','1', sendErrorMessage.mail_for_blocked_email) }} + {{ ExpandedForm.checkbox('mail_for_bad_login','1', sendErrorMessage.mail_for_bad_login) }} + {{ ExpandedForm.checkbox('mail_for_blocked_login','1', sendErrorMessage.mail_for_blocked_login) }} +
+
+
+
diff --git a/resources/views/emails/blocked-bad-creds-html.twig b/resources/views/emails/blocked-bad-creds-html.twig new file mode 100644 index 0000000000..4485093a9b --- /dev/null +++ b/resources/views/emails/blocked-bad-creds-html.twig @@ -0,0 +1,5 @@ +{% include 'emails.header-html' %} +

+ Firefly III has just blocked a login from user with email "{{ email }}" because they supplied bad credentials. +

+{% include 'emails.footer-html' %} diff --git a/resources/views/emails/blocked-bad-creds-text.twig b/resources/views/emails/blocked-bad-creds-text.twig new file mode 100644 index 0000000000..e0de014cff --- /dev/null +++ b/resources/views/emails/blocked-bad-creds-text.twig @@ -0,0 +1,3 @@ +{% include 'emails.header-text' %} +Firefly III has just blocked a login from user with email "{{ email }}" because they supplied bad credentials. +{% include 'emails.footer-text' %} diff --git a/resources/views/emails/locked-out-html.twig b/resources/views/emails/locked-out-html.twig new file mode 100644 index 0000000000..8f4386dacc --- /dev/null +++ b/resources/views/emails/locked-out-html.twig @@ -0,0 +1,5 @@ +{% include 'emails.header-html' %} +

+ Firefly III has just locked out somebody trying to login with email address {{ email }}. +

+{% include 'emails.footer-html' %} diff --git a/resources/views/emails/locked-out-text.twig b/resources/views/emails/locked-out-text.twig new file mode 100644 index 0000000000..6d5b1bc41c --- /dev/null +++ b/resources/views/emails/locked-out-text.twig @@ -0,0 +1,3 @@ +{% include 'emails.header-text' %} +Firefly III has just locked out somebody trying to login with email address {{ email }}. +{% include 'emails.footer-text' %} \ No newline at end of file