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
*/
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;
}
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')
{
$hash = md5(strtolower(trim($emailAddress)));
@ -68,4 +85,14 @@ class MiscHelper
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\Helpers\MiscHelper;
use App\Http\Requests\StoreUserRequest;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
@ -11,7 +12,7 @@ use Illuminate\Support\Facades\DB;
class InstallController extends Controller
{
public function administrator(Request $request)
public function administrator(StoreUserRequest $request)
{
// Validate we're at the required stage
$stage = 3;
@ -20,6 +21,16 @@ class InstallController extends Controller
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')
{
$user = new User();
@ -30,10 +41,16 @@ class InstallController extends Controller
$user->is_activated = true;
$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)
@ -47,6 +64,15 @@ class InstallController extends Controller
}
$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 = [
'curl' => 'installer.php_modules.curl',
'pdo_mysql' => 'installer.php_modules.mysql',
@ -71,6 +97,9 @@ class InstallController extends Controller
return view('install.check', [
'available_modules' => $availableModules,
'can_continue' => $canContinue,
'php_is_valid' => $phpIsValid,
'php_version_current' => $runtimeVersion,
'php_version_required' => $runtimeMinimum,
'post_max_size' => ($postMaxSize / 1024 / 1024),
'post_max_size_warning' => $postMaxSize < $recommendedMinimum,
'recommended_minimum_upload' => ($recommendedMinimum / 1024 / 1024),
@ -126,7 +155,7 @@ class InstallController extends Controller
Artisan::call('cache:clear');
Artisan::call('migrate', ['--force' => true]);
$result = Configuration::where('key', 'install_date')->first();
$result = Configuration::where('key', 'install_completed')->first();
if (is_null($result))
{
$result = new Configuration();
@ -135,6 +164,8 @@ class InstallController extends Controller
$result->save();
}
// Now the database is up-to-date, we can enable database sessions
$request->session()->set('install_stage', 3);
return redirect(route('install.administrator'));
}

View File

@ -44,22 +44,25 @@ class AppInstallation
return $next($request);
}
// See if the successful flag has been written to the .env file
$isAppInstalled = MiscHelper::getEnvironmentSetting('APP_INSTALLED');
if ($request->is('install/*'))
{
// 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);
}
try
if ($isAppInstalled)
{
if (Configuration::installCompleted())
{
return $next($request);
}
}
catch (\Exception $ex)
{
// Empty catch block to allow falling through to the redirect below
// App is configured, continue on
return $next($request);
}
return redirect(route('install.check'));

View File

@ -7,6 +7,7 @@ use App\Facade\Theme;
use App\Facade\UserConfig;
use App\Helpers\DbHelper;
use Closure;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Foundation\Application;
use Illuminate\Http\Request;
use Illuminate\Mail\Mailer;
@ -67,8 +68,8 @@ class GlobalConfiguration
if (function_exists('sg_get_const'))
{
$licenseName = sg_get_const('license_name');
$licenseNo = sg_get_const('license_number');
$licenseName = sg_get_const('lic_name');
$licenseNo = sg_get_const('lic_num');
}
View::share('license_name', strlen($licenseName) == 0 ? '**UNLICENSED**' : $licenseName);
@ -110,7 +111,14 @@ class GlobalConfiguration
$password = UserConfig::get('smtp_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'))

View File

@ -10,6 +10,10 @@
RewriteCond %{REQUEST_FILENAME} !-d
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...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f

View File

@ -41,6 +41,7 @@ echo 'Removing build files...' . PHP_EOL . PHP_EOL;
// Remove development-related files
system(sprintf('rm -rf %s/resources/build', $appRoot));
system(sprintf('rm -rf %s/tests', $appRoot));
@unlink(sprintf('%s/composer.phar', $appRoot));
@unlink(sprintf('%s/server.php', $appRoot));
// 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_title' => 'Create an administrator account',
'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_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' => [
'heading' => 'PHP configuration:',
'post_max_size' => 'Maximum POST request size:',

View File

@ -4,9 +4,19 @@
<h3>@lang('installer.administrator_title')</h3>
<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="col-md-6 col-md-offset-3">
<form action="{{ url('/install') }}" method="post">
<form action="{{ route('install.administrator') }}" method="post">
{{ csrf_field() }}
<div class="form-group">
<label for="name">Name:</label>
@ -25,10 +35,13 @@
<div class="form-group">
<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 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>
</div>
</form>

View File

@ -8,9 +8,20 @@
<div class="col-md-8 col-md-offset-2">
<table class="table table-striped">
<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>
<td><b>@lang('installer.php_modules.heading')</b></td>
<td style="width: 15%;"></td>
<td></td>
</tr>
@foreach ($required_modules as $key => $value)
<tr>

View File

@ -4,8 +4,8 @@
<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('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('admin.settings') }}"><i class="fa fa-fw fa-cog"></i> @lang('navigation.breadcrumb.settings')</a></li>
</ul>
</div>
</div>

View File

@ -3,5 +3,27 @@
"version": "1.0",
"author": "Andy Heathershaw",
"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
}
]
}