diff --git a/.env.example b/.env.example index d53cb52a46..0b00b9ba19 100644 --- a/.env.example +++ b/.env.example @@ -177,6 +177,13 @@ LOGIN_PROVIDER=eloquent # This function is available in Firefly III v5.3.0 and higher. AUTHENTICATION_GUARD=web +# +# Likewise, it's impossible to log out users who's authentication is handled by an external system. +# Enter a custom URL here that will force a logout (your authentication provider can tell you). +# Setting this variable only works when AUTHENTICATION_GUARD != web +# +CUSTOM_LOGOUT_URI= + # LDAP connection configuration # OpenLDAP, FreeIPA or ActiveDirectory # # If you use Docker or similar, you can set this variable from a file by appending it with _FILE diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 6db9e4f5b1..5c359e40d2 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -176,4 +176,35 @@ class LoginController extends Controller throw $exception; } + + /** + * Log the user out of the application. + * + * @param \Illuminate\Http\Request $request + * + * @return \Illuminate\Http\Response + */ + public function logout(Request $request) + { + $authGuard = config('firefly.authentication_guard'); + $logoutUri = config('firefly.custom_logout_uri'); + if ('remote_user_guard' === $authGuard && '' !== $logoutUri) { + return redirect($logoutUri); + } + + $this->guard()->logout(); + + $request->session()->invalidate(); + + $request->session()->regenerateToken(); + + if ($response = $this->loggedOut($request)) { + return $response; + } + + return $request->wantsJson() + ? new \Illuminate\Http\Response('', 204) + : redirect('/'); + } + } diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 9c84f76e5a..6cd342aaba 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -61,6 +61,8 @@ class ProfileController extends Controller { use RequestInformation, CreateStuff; + protected bool $externalIdentity; + /** * ProfileController constructor. * @@ -78,6 +80,9 @@ class ProfileController extends Controller return $next($request); } ); + $loginProvider = config('firefly.login_provider'); + $authGuard = config('firefly.authentication_guard'); + $this->externalIdentity = 'eloquent' === $loginProvider || 'remote_user_guard' === $authGuard; $this->middleware(IsDemoUser::class)->except(['index']); $this->middleware(IsSandStormUser::class)->except('index'); @@ -92,13 +97,10 @@ class ProfileController extends Controller */ public function changeEmail(Request $request) { - $loginProvider = config('firefly.login_provider'); - if ('eloquent' !== $loginProvider) { - // @codeCoverageIgnoreStart - $request->session()->flash('error', trans('firefly.login_provider_local_only', ['login_provider' => e($loginProvider)])); + if ($this->externalIdentity) { + $request->session()->flash('error', trans('firefly.external_user_mgt_disabled')); return redirect(route('profile.index')); - // @codeCoverageIgnoreEnd } $title = auth()->user()->email; @@ -118,13 +120,10 @@ class ProfileController extends Controller */ public function changePassword(Request $request) { - $loginProvider = config('firefly.login_provider'); - if ('eloquent' !== $loginProvider) { - // @codeCoverageIgnoreStart - $request->session()->flash('error', trans('firefly.login_provider_local_only', ['login_provider' => e($loginProvider)])); + if ($this->externalIdentity) { + $request->session()->flash('error', trans('firefly.external_user_mgt_disabled')); return redirect(route('profile.index')); - // @codeCoverageIgnoreEnd } $title = auth()->user()->email; @@ -137,10 +136,17 @@ class ProfileController extends Controller /** * View that generates a 2FA code for the user. * + * @param Request $request + * * @return Factory|View */ - public function code() + public function code(Request $request) { + if ($this->externalIdentity) { + $request->session()->flash('error', trans('firefly.external_user_mgt_disabled')); + + return redirect(route('profile.index')); + } $domain = $this->getDomain(); $secret = null; @@ -192,10 +198,9 @@ class ProfileController extends Controller */ public function confirmEmailChange(UserRepositoryInterface $repository, string $token) { - $loginProvider = config('firefly.login_provider'); - if ('eloquent' !== $loginProvider) { + if ($this->externalIdentity) { // @codeCoverageIgnoreStart - throw new FireflyException('Cannot confirm email change when authentication provider is not local.'); + throw new FireflyException(trans('firefly.external_user_mgt_disabled')); // @codeCoverageIgnoreEnd } // find preference with this token value. @@ -229,15 +234,14 @@ class ProfileController extends Controller * * @param Request $request * - * @return Factory|View + * @return \Illuminate\Contracts\Foundation\Application|RedirectResponse|Redirector */ public function deleteAccount(Request $request) { - $loginProvider = config('firefly.login_provider'); - if ('eloquent' !== $loginProvider) { - // @codeCoverageIgnoreStart - $request->session()->flash('warning', trans('firefly.delete_local_info_only', ['login_provider' => e($loginProvider)])); - // @codeCoverageIgnoreEnd + if ($this->externalIdentity) { + $request->session()->flash('error', trans('firefly.external_user_mgt_disabled')); + + return redirect(route('profile.index')); } $title = auth()->user()->email; $subTitle = (string) trans('firefly.delete_account'); @@ -251,8 +255,13 @@ class ProfileController extends Controller * * @return RedirectResponse|Redirector */ - public function deleteCode() + public function deleteCode(Request $request) { + if ($this->externalIdentity) { + $request->session()->flash('error', trans('firefly.external_user_mgt_disabled')); + + return redirect(route('profile.index')); + } /** @var UserRepositoryInterface $repository */ $repository = app(UserRepositoryInterface::class); @@ -271,8 +280,14 @@ class ProfileController extends Controller * * @return RedirectResponse|Redirector */ - public function enable2FA() + public function enable2FA(Request $request) { + if ($this->externalIdentity) { + $request->session()->flash('error', trans('firefly.external_user_mgt_disabled')); + + return redirect(route('profile.index')); + } + /** @var User $user */ $user = auth()->user(); $enabledMFA = null !== $user->mfa_secret; @@ -329,6 +344,12 @@ class ProfileController extends Controller */ public function newBackupCodes() { + if ($this->externalIdentity) { + $request->session()->flash('error', trans('firefly.external_user_mgt_disabled')); + + return redirect(route('profile.index')); + } + // generate recovery codes: $recovery = app(Recovery::class); $recoveryCodes = $recovery->lowercase() @@ -354,13 +375,10 @@ class ProfileController extends Controller */ public function postChangeEmail(EmailFormRequest $request, UserRepositoryInterface $repository) { - $loginProvider = config('firefly.login_provider'); - if ('eloquent' !== $loginProvider) { - // @codeCoverageIgnoreStart - $request->session()->flash('error', trans('firefly.login_provider_local_only', ['login_provider' => e($loginProvider)])); + if ($this->externalIdentity) { + $request->session()->flash('error', trans('firefly.external_user_mgt_disabled')); return redirect(route('profile.index')); - // @codeCoverageIgnoreEnd } /** @var User $user */ @@ -408,13 +426,10 @@ class ProfileController extends Controller */ public function postChangePassword(ProfileFormRequest $request, UserRepositoryInterface $repository) { - $loginProvider = config('firefly.login_provider'); - if ('eloquent' !== $loginProvider) { - // @codeCoverageIgnoreStart - $request->session()->flash('error', trans('firefly.login_provider_local_only', ['login_provider' => e($loginProvider)])); + if ($this->externalIdentity) { + $request->session()->flash('error', trans('firefly.external_user_mgt_disabled')); return redirect(route('profile.index')); - // @codeCoverageIgnoreEnd } // the request has already validated both new passwords must be equal. @@ -446,6 +461,12 @@ class ProfileController extends Controller */ public function postCode(TokenFormRequest $request) { + if ($this->externalIdentity) { + $request->session()->flash('error', trans('firefly.external_user_mgt_disabled')); + + return redirect(route('profile.index')); + } + /** @var User $user */ $user = auth()->user(); /** @var UserRepositoryInterface $repository */ @@ -485,6 +506,12 @@ class ProfileController extends Controller */ public function postDeleteAccount(UserRepositoryInterface $repository, DeleteAccountFormRequest $request) { + if ($this->externalIdentity) { + $request->session()->flash('error', trans('firefly.external_user_mgt_disabled')); + + return redirect(route('profile.index')); + } + if (!Hash::check($request->get('password'), auth()->user()->password)) { session()->flash('error', (string) trans('firefly.invalid_password')); @@ -506,8 +533,14 @@ class ProfileController extends Controller * * @return RedirectResponse|Redirector */ - public function regenerate() + public function regenerate(Request $request) { + if ($this->externalIdentity) { + $request->session()->flash('error', trans('firefly.external_user_mgt_disabled')); + + return redirect(route('profile.index')); + } + /** @var User $user */ $user = auth()->user(); $token = $user->generateAccessToken(); @@ -530,11 +563,8 @@ class ProfileController extends Controller */ public function undoEmailChange(UserRepositoryInterface $repository, string $token, string $hash) { - $loginProvider = config('firefly.login_provider'); - if ('eloquent' !== $loginProvider) { - // @codeCoverageIgnoreStart - throw new FireflyException('Cannot confirm email change when authentication provider is not local.'); - // @codeCoverageIgnoreEnd + if ($this->externalIdentity) { + throw new FireflyException(trans('firefly.external_user_mgt_disabled')); } // find preference with this token value. diff --git a/config/firefly.php b/config/firefly.php index 37f885f583..e8e7c950ac 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -83,14 +83,14 @@ use FireflyIII\TransactionRules\Triggers\AmountMore; use FireflyIII\TransactionRules\Triggers\BudgetIs; use FireflyIII\TransactionRules\Triggers\CategoryIs; use FireflyIII\TransactionRules\Triggers\CurrencyIs; -use FireflyIII\TransactionRules\Triggers\ForeignCurrencyIs; -use FireflyIII\TransactionRules\Triggers\DateIs; -use FireflyIII\TransactionRules\Triggers\DateBefore; use FireflyIII\TransactionRules\Triggers\DateAfter; +use FireflyIII\TransactionRules\Triggers\DateBefore; +use FireflyIII\TransactionRules\Triggers\DateIs; use FireflyIII\TransactionRules\Triggers\DescriptionContains; use FireflyIII\TransactionRules\Triggers\DescriptionEnds; use FireflyIII\TransactionRules\Triggers\DescriptionIs; use FireflyIII\TransactionRules\Triggers\DescriptionStarts; +use FireflyIII\TransactionRules\Triggers\ForeignCurrencyIs; use FireflyIII\TransactionRules\Triggers\FromAccountContains; use FireflyIII\TransactionRules\Triggers\FromAccountEnds; use FireflyIII\TransactionRules\Triggers\FromAccountIs; @@ -142,10 +142,10 @@ return [ ], 'encryption' => null === env('USE_ENCRYPTION') || true === env('USE_ENCRYPTION'), - 'version' => '5.3.0', - 'api_version' => '1.1.0', - 'db_version' => 14, - 'maxUploadSize' => 15242880, + 'version' => '5.3.0', + 'api_version' => '1.1.0', + 'db_version' => 14, + 'maxUploadSize' => 15242880, 'send_error_message' => env('SEND_ERROR_MESSAGE', true), 'site_owner' => env('SITE_OWNER', ''), 'send_registration_mail' => env('SEND_REGISTRATION_MAIL', true), @@ -162,6 +162,8 @@ return [ 'disable_frame_header' => env('DISABLE_FRAME_HEADER', false), 'disable_csp_header' => env('DISABLE_CSP_HEADER', false), 'login_provider' => envNonEmpty('LOGIN_PROVIDER', 'eloquent'), + 'authentication_guard' => envNonEmpty('AUTHENTICATION_GUARD', 'web'), + 'custom_logout_uri' => envNonEmpty('CUSTOM_LOGOUT_URI', ''), 'cer_provider' => envNonEmpty('CER_PROVIDER', 'fixer'), 'update_endpoint' => 'https://version.firefly-iii.org/index.json', 'send_telemetry' => env('SEND_TELEMETRY', false), diff --git a/config/view.php b/config/view.php index 871076995c..ce48cdcf8f 100644 --- a/config/view.php +++ b/config/view.php @@ -48,6 +48,6 @@ return [ | */ - 'compiled' => realpath(storage_path('framework/views/v1')), + 'compiled' => realpath(storage_path(sprintf('framework/views/%s', env('FIREFLY_III_LAYOUT', 'v1')))), ]; diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 6e586c0832..f71931885b 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -655,7 +655,8 @@ return [ 'login_with_new_email' => 'You can now login with your new email address.', 'login_with_old_email' => 'You can now login with your old email address again.', 'login_provider_local_only' => 'This action is not available when authenticating through ":login_provider".', - 'delete_local_info_only' => "Because you authenticate through ':login_provider', this will only delete local Firefly III information.", + 'external_user_mgt_disabled' => 'This action is not available when Firefly III isn\'t responsible for user management or authentication handling.', + 'delete_local_info_only' => "Because Firefly III isn't responsible for user management or authentication handling, this function will only delete local Firefly III information.", 'profile_oauth_clients' => 'OAuth Clients', 'profile_oauth_no_clients' => 'You have not created any OAuth clients.', 'profile_oauth_clients_header' => 'Clients', @@ -1667,6 +1668,6 @@ return [ 'debug_submit_instructions' => 'If you are running into problems, you can use the information in this box as debug information. Please copy-and-paste into a new or existing GitHub issue. It will generate a beautiful table that can be used to quickly diagnose your problem.', 'debug_pretty_table' => 'If you copy/paste the box below into a GitHub issue it will generate a table. Please do not surround this text with backticks or quotes.', 'debug_additional_data' => 'You may also share the content of the box below. You can also copy-and-paste this into a new or existing GitHub issue. However, the content of this box may contain private information such as account names, transaction details or email addresses.', - + ];