resolves #5: modified the installer to write a flag to the .env file when installed so naughty people can't create admin accounts etc by directly accessing the installer. First version tested fully encoded with SG :)

This commit is contained in:
Andy Heathershaw 2016-10-06 15:07:30 +01:00
parent 227de6baf6
commit e81dfcd1fd
12 changed files with 146 additions and 26 deletions

View File

@ -32,9 +32,4 @@ class Configuration extends Model
* @var string * @var string
*/ */
protected $table = 'configuration'; protected $table = 'configuration';
public static function installCompleted()
{
return (!is_null(Configuration::where('key', 'install_completed')->first()));
}
} }

View File

@ -37,6 +37,23 @@ class MiscHelper
return (int) $val; return (int) $val;
} }
public static function getEnvironmentFilePath()
{
return sprintf('%s/.env', dirname(dirname(__DIR__)));
}
public static function getEnvironmentSetting($settingName)
{
$envFile = MiscHelper::getEnvironmentFilePath();
$matches = null;
if (preg_match(sprintf('/^\s*%s\s*=\s*(.+)$/im', preg_quote($settingName)), file_get_contents($envFile), $matches))
{
return trim($matches[1]);
}
return null;
}
public static function gravatarUrl($emailAddress, $size = 48, $default = 'identicon') public static function gravatarUrl($emailAddress, $size = 48, $default = 'identicon')
{ {
$hash = md5(strtolower(trim($emailAddress))); $hash = md5(strtolower(trim($emailAddress)));
@ -68,4 +85,14 @@ class MiscHelper
return $string; return $string;
} }
public static function setEnvironmentSetting($settingName, $value)
{
if (is_null(MiscHelper::getEnvironmentSetting($settingName)))
{
return file_put_contents(MiscHelper::getEnvironmentFilePath(), sprintf('%s=%s', $settingName, $value) . PHP_EOL, FILE_APPEND);
}
return false;
}
} }

View File

@ -4,6 +4,7 @@ namespace App\Http\Controllers;
use App\Configuration; use App\Configuration;
use App\Helpers\MiscHelper; use App\Helpers\MiscHelper;
use App\Http\Requests\StoreUserRequest;
use App\User; use App\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Artisan;
@ -11,7 +12,7 @@ use Illuminate\Support\Facades\DB;
class InstallController extends Controller class InstallController extends Controller
{ {
public function administrator(Request $request) public function administrator(StoreUserRequest $request)
{ {
// Validate we're at the required stage // Validate we're at the required stage
$stage = 3; $stage = 3;
@ -20,6 +21,16 @@ class InstallController extends Controller
return redirect(route('install.check')); return redirect(route('install.check'));
} }
// If we already have an admin account, this step can be skipped
$canSkip = User::where('is_admin', true)->count() > 0;
if ($canSkip && $request->has('skip'))
{
MiscHelper::setEnvironmentSetting('APP_INSTALLED', true);
return redirect(route('home'));
}
if ($request->method() == 'POST') if ($request->method() == 'POST')
{ {
$user = new User(); $user = new User();
@ -30,10 +41,16 @@ class InstallController extends Controller
$user->is_activated = true; $user->is_activated = true;
$user->save(); $user->save();
return redirect(url('login')); MiscHelper::setEnvironmentSetting('APP_INSTALLED', true);
$request->session()->flash('success', trans('installer.install_completed_message'));
return redirect(route('home'));
} }
return view('install.administrator'); return view('install.administrator', [
'can_skip' => $canSkip
]);
} }
public function check(Request $request) public function check(Request $request)
@ -47,6 +64,15 @@ class InstallController extends Controller
} }
$canContinue = true; $canContinue = true;
$runtimeMinimum = '5.6.4'; // this minimum is imposed by Laravel 5.3
$runtimeVersion = phpversion();
$phpIsValid = version_compare($runtimeVersion, $runtimeMinimum) >= 0;
if (!$phpIsValid)
{
$canContinue = false;
}
$requiredModules = [ $requiredModules = [
'curl' => 'installer.php_modules.curl', 'curl' => 'installer.php_modules.curl',
'pdo_mysql' => 'installer.php_modules.mysql', 'pdo_mysql' => 'installer.php_modules.mysql',
@ -71,6 +97,9 @@ class InstallController extends Controller
return view('install.check', [ return view('install.check', [
'available_modules' => $availableModules, 'available_modules' => $availableModules,
'can_continue' => $canContinue, 'can_continue' => $canContinue,
'php_is_valid' => $phpIsValid,
'php_version_current' => $runtimeVersion,
'php_version_required' => $runtimeMinimum,
'post_max_size' => ($postMaxSize / 1024 / 1024), 'post_max_size' => ($postMaxSize / 1024 / 1024),
'post_max_size_warning' => $postMaxSize < $recommendedMinimum, 'post_max_size_warning' => $postMaxSize < $recommendedMinimum,
'recommended_minimum_upload' => ($recommendedMinimum / 1024 / 1024), 'recommended_minimum_upload' => ($recommendedMinimum / 1024 / 1024),
@ -126,7 +155,7 @@ class InstallController extends Controller
Artisan::call('cache:clear'); Artisan::call('cache:clear');
Artisan::call('migrate', ['--force' => true]); Artisan::call('migrate', ['--force' => true]);
$result = Configuration::where('key', 'install_date')->first(); $result = Configuration::where('key', 'install_completed')->first();
if (is_null($result)) if (is_null($result))
{ {
$result = new Configuration(); $result = new Configuration();
@ -135,6 +164,8 @@ class InstallController extends Controller
$result->save(); $result->save();
} }
// Now the database is up-to-date, we can enable database sessions
$request->session()->set('install_stage', 3); $request->session()->set('install_stage', 3);
return redirect(route('install.administrator')); return redirect(route('install.administrator'));
} }

View File

@ -44,22 +44,25 @@ class AppInstallation
return $next($request); return $next($request);
} }
// See if the successful flag has been written to the .env file
$isAppInstalled = MiscHelper::getEnvironmentSetting('APP_INSTALLED');
if ($request->is('install/*')) if ($request->is('install/*'))
{ {
// Already in the installer // Already in the installer
// For security reasons, don't allow the installer to be used if it has been previously completed
if (boolval($isAppInstalled))
{
return redirect(route('home'));
}
return $next($request); return $next($request);
} }
try if ($isAppInstalled)
{ {
if (Configuration::installCompleted()) // App is configured, continue on
{ return $next($request);
return $next($request);
}
}
catch (\Exception $ex)
{
// Empty catch block to allow falling through to the redirect below
} }
return redirect(route('install.check')); return redirect(route('install.check'));

View File

@ -7,6 +7,7 @@ use App\Facade\Theme;
use App\Facade\UserConfig; use App\Facade\UserConfig;
use App\Helpers\DbHelper; use App\Helpers\DbHelper;
use Closure; use Closure;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Foundation\Application; use Illuminate\Foundation\Application;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Mail\Mailer; use Illuminate\Mail\Mailer;
@ -67,8 +68,8 @@ class GlobalConfiguration
if (function_exists('sg_get_const')) if (function_exists('sg_get_const'))
{ {
$licenseName = sg_get_const('license_name'); $licenseName = sg_get_const('lic_name');
$licenseNo = sg_get_const('license_number'); $licenseNo = sg_get_const('lic_num');
} }
View::share('license_name', strlen($licenseName) == 0 ? '**UNLICENSED**' : $licenseName); View::share('license_name', strlen($licenseName) == 0 ? '**UNLICENSED**' : $licenseName);
@ -110,7 +111,14 @@ class GlobalConfiguration
$password = UserConfig::get('smtp_password'); $password = UserConfig::get('smtp_password');
if (!is_null($password)) if (!is_null($password))
{ {
$transport->setPassword(decrypt($password)); try
{
$transport->setPassword(decrypt($password));
}
catch (DecryptException $ex)
{
// Unable to decrypt the password - presumably the app's key has changed
}
} }
if (UserConfig::get('smtp_encryption')) if (UserConfig::get('smtp_encryption'))

View File

@ -10,6 +10,10 @@
RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)/$ /$1 [L,R=301] RewriteRule ^(.*)/$ /$1 [L,R=301]
# Remove index.php from URL...
RewriteCond %{THE_REQUEST} ^GET.*index\.php [NC]
RewriteRule (.*?)index\.php/*(.*) /$1$2 [R=301,NE,L]
# Handle Front Controller... # Handle Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-f

View File

@ -41,6 +41,7 @@ echo 'Removing build files...' . PHP_EOL . PHP_EOL;
// Remove development-related files // Remove development-related files
system(sprintf('rm -rf %s/resources/build', $appRoot)); system(sprintf('rm -rf %s/resources/build', $appRoot));
system(sprintf('rm -rf %s/tests', $appRoot)); system(sprintf('rm -rf %s/tests', $appRoot));
@unlink(sprintf('%s/composer.phar', $appRoot));
@unlink(sprintf('%s/server.php', $appRoot)); @unlink(sprintf('%s/server.php', $appRoot));
// Can't use Artisan once encoded // Can't use Artisan once encoded

View File

@ -3,8 +3,13 @@ return [
'administrator_intro' => 'You will need an administrator account to access Blue Twilight. Complete the form below to create your administrator account.', 'administrator_intro' => 'You will need an administrator account to access Blue Twilight. Complete the form below to create your administrator account.',
'administrator_title' => 'Create an administrator account', 'administrator_title' => 'Create an administrator account',
'app_name' => 'Blue Twilight - Install', 'app_name' => 'Blue Twilight - Install',
'core' => [
'heading' => 'Core Requirements',
'php_version' => 'Requires PHP :minimum minimum'
],
'database_intro' => 'Please provide the connection details for an empty MySQL or MariaDB database.', 'database_intro' => 'Please provide the connection details for an empty MySQL or MariaDB database.',
'database_title' => 'Connect to a Database', 'database_title' => 'Connect to a Database',
'install_completed_message' => 'Congratulations, Blue Twilight has been installed successfully. You can now login with an administrator account using the "Login" link above.',
'php_config' => [ 'php_config' => [
'heading' => 'PHP configuration:', 'heading' => 'PHP configuration:',
'post_max_size' => 'Maximum POST request size:', 'post_max_size' => 'Maximum POST request size:',

View File

@ -4,9 +4,19 @@
<h3>@lang('installer.administrator_title')</h3> <h3>@lang('installer.administrator_title')</h3>
<p>@lang('installer.administrator_intro')</p> <p>@lang('installer.administrator_intro')</p>
@if (count($errors) > 0)
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $form_error)
<li>{{ $form_error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="row" style="margin-top: 30px;"> <div class="row" style="margin-top: 30px;">
<div class="col-md-6 col-md-offset-3"> <div class="col-md-6 col-md-offset-3">
<form action="{{ url('/install') }}" method="post"> <form action="{{ route('install.administrator') }}" method="post">
{{ csrf_field() }} {{ csrf_field() }}
<div class="form-group"> <div class="form-group">
<label for="name">Name:</label> <label for="name">Name:</label>
@ -25,10 +35,13 @@
<div class="form-group"> <div class="form-group">
<label for="password-confirm">Confirm Password:</label> <label for="password-confirm">Confirm Password:</label>
<input type="password" class="form-control" id="password-confirm" name="password-confirm" value="{{ old('password-confirm') }}" /> <input type="password" class="form-control" id="password-confirm" name="password_confirmation" value="{{ old('password-confirm') }}" />
</div> </div>
<div class="form-group text-right"> <div class="form-group text-right">
@if ($can_skip)
<a class="btn btn-default" href="{{ route('install.administrator', ['skip' => 1]) }}">Skip</a>
@endif
<button type="submit" class="btn btn-success">Create account</button> <button type="submit" class="btn btn-success">Create account</button>
</div> </div>
</form> </form>

View File

@ -8,9 +8,20 @@
<div class="col-md-8 col-md-offset-2"> <div class="col-md-8 col-md-offset-2">
<table class="table table-striped"> <table class="table table-striped">
<tbody> <tbody>
<tr>
<td><b>@lang('installer.core.heading')</b></td>
<td style="width: 30%;"></td>
</tr>
<tr>
<td>@lang('installer.core.php_version', ['minimum' => $php_version_required])</td>
<td>
{{ $php_version_current }}
<i class="fa fa-fw fa-{{ $php_is_valid ? 'check text-success' : 'times text-danger' }}"></i>
</td>
</tr>
<tr> <tr>
<td><b>@lang('installer.php_modules.heading')</b></td> <td><b>@lang('installer.php_modules.heading')</b></td>
<td style="width: 15%;"></td> <td></td>
</tr> </tr>
@foreach ($required_modules as $key => $value) @foreach ($required_modules as $key => $value)
<tr> <tr>

View File

@ -4,8 +4,8 @@
<ul class="nav nav-pills"> <ul class="nav nav-pills">
<li role="presentation"><a href="{{ route('albums.index') }}"><i class="fa fa-fw fa-picture-o"></i> @lang('navigation.breadcrumb.albums')</a></li> <li role="presentation"><a href="{{ route('albums.index') }}"><i class="fa fa-fw fa-picture-o"></i> @lang('navigation.breadcrumb.albums')</a></li>
<li role="presentation"><a href="{{ route('user.index') }}"><i class="fa fa-fw fa-user"></i> @lang('navigation.breadcrumb.users')</a></li> <li role="presentation"><a href="{{ route('user.index') }}"><i class="fa fa-fw fa-user"></i> @lang('navigation.breadcrumb.users')</a></li>
<li role="presentation"><a href="{{ route('admin.settings') }}"><i class="fa fa-fw fa-cog"></i> @lang('navigation.breadcrumb.settings')</a></li>
<li role="presentation"><a href="{{ route('storage.index') }}"><i class="fa fa-fw fa-folder"></i> @lang('navigation.breadcrumb.storage')</a></li> <li role="presentation"><a href="{{ route('storage.index') }}"><i class="fa fa-fw fa-folder"></i> @lang('navigation.breadcrumb.storage')</a></li>
<li role="presentation"><a href="{{ route('admin.settings') }}"><i class="fa fa-fw fa-cog"></i> @lang('navigation.breadcrumb.settings')</a></li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -3,5 +3,27 @@
"version": "1.0", "version": "1.0",
"author": "Andy Heathershaw", "author": "Andy Heathershaw",
"author_email": "andy@andys.eu", "author_email": "andy@andys.eu",
"navbar_class": "navbar-default navbar-static-top" "navbar_class": "navbar-default navbar-static-top",
"thumbnails": [
{
"name": "fullsize",
"height": 600,
"width": 800
},
{
"name": "preview",
"height": 300,
"width": 400
},
{
"name": "admin-preview",
"height": 112,
"width": 150
},
{
"name": "slideshow-tiny",
"height": 75,
"width": 100
}
]
} }