From e764023ad8692dcf2eba26673b29d6689afa94c0 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Fri, 28 Dec 2018 13:56:53 +0000 Subject: [PATCH] Deal with settings read/write errors properly --- .../Displayers/SettingsDisplayer.php | 89 +++++++++++++++++++ app/Http/Middleware/ReadyForUse.php | 2 +- app/Http/Middleware/SetupAlreadyCompleted.php | 9 +- app/Settings/ReadException.php | 34 +++++++ app/Settings/Repository.php | 57 +++++++++--- app/Settings/SettingsException.php | 35 ++++++++ app/Settings/WriteException.php | 34 +++++++ config/exceptions.php | 5 +- 8 files changed, 247 insertions(+), 18 deletions(-) create mode 100644 app/Foundation/Exceptions/Displayers/SettingsDisplayer.php create mode 100644 app/Settings/ReadException.php create mode 100644 app/Settings/SettingsException.php create mode 100644 app/Settings/WriteException.php diff --git a/app/Foundation/Exceptions/Displayers/SettingsDisplayer.php b/app/Foundation/Exceptions/Displayers/SettingsDisplayer.php new file mode 100644 index 00000000..9b896d16 --- /dev/null +++ b/app/Foundation/Exceptions/Displayers/SettingsDisplayer.php @@ -0,0 +1,89 @@ +request = $request; + } + + /** + * Get the error response associated with the given exception. + * + * @param \Exception $exception + * @param string $id + * @param int $code + * @param string[] $headers + * + * @return \Symfony\Component\HttpFoundation\Response + */ + public function display(Exception $exception, string $id, int $code, array $headers) + { + return cachet_redirect('setup'); + } + + /** + * Get the supported content type. + * + * @return string + */ + public function contentType() + { + return 'text/html'; + } + + /** + * Can we display the exception? + * + * @param \Exception $original + * @param \Exception $transformed + * @param int $code + * + * @return bool + */ + public function canDisplay(Exception $original, Exception $transformed, int $code) + { + return ($transformed instanceof ReadException) && !$this->request->is('setup*'); + } + + /** + * Do we provide verbose information about the exception? + * + * @return bool + */ + public function isVerbose() + { + return false; + } +} diff --git a/app/Http/Middleware/ReadyForUse.php b/app/Http/Middleware/ReadyForUse.php index 3acfe8d2..1ae77e0c 100644 --- a/app/Http/Middleware/ReadyForUse.php +++ b/app/Http/Middleware/ReadyForUse.php @@ -53,7 +53,7 @@ class ReadyForUse */ public function handle(Request $request, Closure $next) { - if (!$this->settings->get('app_name')) { + if (!$request->is('setup*') && !$this->settings->get('app_name')) { return cachet_redirect('setup'); } diff --git a/app/Http/Middleware/SetupAlreadyCompleted.php b/app/Http/Middleware/SetupAlreadyCompleted.php index 83de0ad2..0a7be715 100644 --- a/app/Http/Middleware/SetupAlreadyCompleted.php +++ b/app/Http/Middleware/SetupAlreadyCompleted.php @@ -11,6 +11,7 @@ namespace CachetHQ\Cachet\Http\Middleware; +use CachetHQ\Cachet\Settings\ReadException; use CachetHQ\Cachet\Settings\Repository; use Closure; use Illuminate\Http\Request; @@ -53,8 +54,12 @@ class SetupAlreadyCompleted */ public function handle(Request $request, Closure $next) { - if ($this->settings->get('app_name')) { - return cachet_redirect('dashboard'); + try { + if ($this->settings->get('app_name')) { + return cachet_redirect('dashboard'); + } + } catch (ReadException $e) { + // not setup then! } return $next($request); diff --git a/app/Settings/ReadException.php b/app/Settings/ReadException.php new file mode 100644 index 00000000..86de0ae3 --- /dev/null +++ b/app/Settings/ReadException.php @@ -0,0 +1,34 @@ + + */ +class ReadException extends SettingsException +{ + /** + * Create a new read exception instance. + * + * @param \Exception $e + * + * @return void + */ + public function __construct(Exception $e) + { + parent::__construct('Unable to read Cachet settings', $e); + } +} diff --git a/app/Settings/Repository.php b/app/Settings/Repository.php index 35cf79e9..e6d54038 100644 --- a/app/Settings/Repository.php +++ b/app/Settings/Repository.php @@ -12,6 +12,7 @@ namespace CachetHQ\Cachet\Settings; use CachetHQ\Cachet\Models\Setting; +use Exception; /** * This is the settings repository class. @@ -59,13 +60,19 @@ class Repository /** * Returns a setting from the database. * + * @throws \CachetHQ\Cachet\Settings\ReadException + * * @return array */ public function all() { - return $this->model->all(['name', 'value'])->pluck('value', 'name')->map(function ($value, $name) { - return $this->castSetting($name, $value); - })->toArray(); + try { + return $this->model->all(['name', 'value'])->pluck('value', 'name')->map(function ($value, $name) { + return $this->castSetting($name, $value); + })->toArray(); + } catch (Exception $e) { + throw new ReadException($e); + } } /** @@ -74,16 +81,22 @@ class Repository * @param string $name * @param string|null $value * + * @throws \CachetHQ\Cachet\Settings\WriteException + * * @return void */ public function set($name, $value) { $this->stale = true; - if ($value === null) { - $this->model->where('name', '=', $name)->delete(); - } else { - $this->model->updateOrCreate(compact('name'), compact('value')); + try { + if ($value === null) { + $this->model->where('name', '=', $name)->delete(); + } else { + $this->model->updateOrCreate(compact('name'), compact('value')); + } + } catch (Exception $e) { + throw new WriteException($e); } } @@ -93,15 +106,21 @@ class Repository * @param string $name * @param mixed $default * + * @throws \CachetHQ\Cachet\Settings\ReadException + * * @return mixed */ public function get($name, $default = null) { - if ($setting = $this->model->where('name', '=', $name)->first()) { - return $this->castSetting($name, $setting->value); - } + try { + if ($setting = $this->model->where('name', '=', $name)->first()) { + return $this->castSetting($name, $setting->value); + } - return $default; + return $default; + } catch (Exception $e) { + throw new ReadException($e); + } } /** @@ -109,25 +128,37 @@ class Repository * * @param string $name * + * @throws \CachetHQ\Cachet\Settings\WriteException + * * @return void */ public function delete($name) { $this->stale = true; - $this->model->where('name', '=', $name)->delete(); + try { + $this->model->where('name', '=', $name)->delete(); + } catch (Exception $e) { + throw new WriteException($e); + } } /** * Clear all settings. * + * @throws \CachetHQ\Cachet\Settings\WriteException + * * @return void */ public function clear() { $this->stale = true; - $this->model->query()->delete(); + try { + $this->model->query()->delete(); + } catch (Exception $e) { + throw new WriteException($e); + } } /** diff --git a/app/Settings/SettingsException.php b/app/Settings/SettingsException.php new file mode 100644 index 00000000..1716ea36 --- /dev/null +++ b/app/Settings/SettingsException.php @@ -0,0 +1,35 @@ + + */ +class SettingsException extends Exception +{ + /** + * Create a new write exception instance. + * + * @param string $m + * @param \Exception $e + * + * @return void + */ + public function __construct(string $m, Exception $e) + { + parent::__construct($m, 0, $e); + } +} diff --git a/app/Settings/WriteException.php b/app/Settings/WriteException.php new file mode 100644 index 00000000..baa41ff7 --- /dev/null +++ b/app/Settings/WriteException.php @@ -0,0 +1,34 @@ + + */ +class WriteException extends SettingsException +{ + /** + * Create a new write exception instance. + * + * @param \Exception $e + * + * @return void + */ + public function __construct(Exception $e) + { + parent::__construct('Unable to write Cachet settings', $e); + } +} diff --git a/config/exceptions.php b/config/exceptions.php index d3187890..7f3a115e 100644 --- a/config/exceptions.php +++ b/config/exceptions.php @@ -47,10 +47,11 @@ return [ */ 'displayers' => [ - 'CachetHQ\Cachet\Foundation\Exceptions\Displayers\JsonValidationDisplayer', + 'CachetHQ\Cachet\Foundation\Exceptions\Displayers\MaintenanceDisplayer', + 'CachetHQ\Cachet\Foundation\Exceptions\Displayers\SettingsDisplayer', 'CachetHQ\Cachet\Foundation\Exceptions\Displayers\RedirectDisplayer', 'CachetHQ\Cachet\Foundation\Exceptions\Displayers\ThrottleDisplayer', - 'CachetHQ\Cachet\Foundation\Exceptions\Displayers\MaintenanceDisplayer', + 'CachetHQ\Cachet\Foundation\Exceptions\Displayers\JsonValidationDisplayer', 'GrahamCampbell\Exceptions\Displayers\DebugDisplayer', 'GrahamCampbell\Exceptions\Displayers\HtmlDisplayer', 'GrahamCampbell\Exceptions\Displayers\JsonDisplayer',