Disable certain dangerous "advanced" features by default in new installs.

Some functionality of AzuraCast has always been intended for "Power Users", but seemingly no amount of warnings or labels will prevent users from discovering these features, misusing them, and either burdening our support channels or declaring AzuraCast to be "broken". With this update, new installations have some of these most dangerous settings (manual port assignments, manual directory selection, custom LS/Icecast config, etc.) disabled. They can easily be re-enabled by editing "azuracast.env" and turning them on, and will remain available for all previous users by default.
This commit is contained in:
Buster "Silver Eagle" Neece 2020-05-27 02:36:30 -05:00
parent ba0d38af6c
commit 0069df6d2d
No known key found for this signature in database
GPG Key ID: 6D9E12FF03411F4E
18 changed files with 141 additions and 49 deletions

View File

@ -56,6 +56,7 @@ ENV VIRTUAL_HOST="azuracast.local" \
# Sensible default environment variables.
ENV APPLICATION_ENV="production" \
ENABLE_ADVANCED_FEATURES="false" \
MYSQL_HOST="mariadb" \
MYSQL_PORT=3306 \
MYSQL_USER="azuracast" \

View File

@ -3,6 +3,7 @@
#
APPLICATION_ENV=development
ENABLE_ADVANCED_FEATURES=true
COMPOSER_PLUGIN_MODE=false
# Developer options.

View File

@ -6,6 +6,18 @@
# Valid options: production, development, testing
APPLICATION_ENV=production
# Enable certain advanced features inside the web interface, including
# advanced playlist coniguration, station port assignment, changing base
# media directory, and other functionality that should only be used by
# users who are comfortable with advanced functionality.
#
# Disclaimer: If this is enabled, you are responsible for fixing any errors
# that are caused by your misuse of advanced features! In many cases, the easiest
# fix is to revert your own changes.
#
# Valid options: true, false
ENABLE_ADVANCED_FEATURES=false
# Enable the composer "merge" functionality to combine the main application's
# composer.json file with any plugins' composer files.
# This can have performance implications, so you should only use it if

View File

@ -167,6 +167,26 @@ return [
'elements' => [
StationFrontendConfiguration::SOURCE_PASSWORD => [
'text',
[
'label' => __('Customize Source Password'),
'description' => __('Leave blank to automatically generate a new password.'),
'belongsTo' => 'frontend_config',
'form_group_class' => 'col-md-6',
],
],
StationFrontendConfiguration::ADMIN_PASSWORD => [
'text',
[
'label' => __('Customize Administrator Password'),
'description' => __('Leave blank to automatically generate a new password.'),
'belongsTo' => 'frontend_config',
'form_group_class' => 'col-md-6',
],
],
StationFrontendConfiguration::PORT => [
'text',
[
@ -189,28 +209,6 @@ return [
],
],
StationFrontendConfiguration::SOURCE_PASSWORD => [
'text',
[
'label' => __('Customize Source Password'),
'label_class' => 'advanced',
'description' => __('Leave blank to automatically generate a new password.'),
'belongsTo' => 'frontend_config',
'form_group_class' => 'col-md-6',
],
],
StationFrontendConfiguration::ADMIN_PASSWORD => [
'text',
[
'label' => __('Customize Administrator Password'),
'label_class' => 'advanced',
'description' => __('Leave blank to automatically generate a new password.'),
'belongsTo' => 'frontend_config',
'form_group_class' => 'col-md-6',
],
],
StationFrontendConfiguration::CUSTOM_CONFIGURATION => [
'textarea',
[

View File

@ -11,6 +11,8 @@ return function (\App\Event\BuildStationMenu $e) {
$backend = $e->getStationBackend();
$frontend = $e->getStationFrontend();
$settings = $e->getSettings();
$e->merge([
'start_station' => [
'label' => __('Start Station'),
@ -158,7 +160,7 @@ return function (\App\Event\BuildStationMenu $e) {
'ls_config' => [
'label' => __('Edit Liquidsoap Configuration'),
'url' => $router->fromHere('stations:util:ls_config'),
'visible' => $backend instanceof App\Radio\Backend\Liquidsoap,
'visible' => $settings->enableAdvancedFeatures() && $backend instanceof App\Radio\Backend\Liquidsoap,
'permission' => Acl::STATION_BROADCASTING,
],
'logs' => [

View File

@ -108,7 +108,7 @@
</b-card>
<edit-modal ref="editModal" :create-url="listUrl" :station-time-zone="stationTimeZone"
@relist="relist"></edit-modal>
:enable-advanced-features="enableAdvancedFeatures" @relist="relist"></edit-modal>
<reorder-modal ref="reorderModal"></reorder-modal>
<import-modal ref="importModal" @relist="relist"></import-modal>
</div>
@ -130,7 +130,8 @@
scheduleUrl: String,
locale: String,
filesUrl: String,
stationTimeZone: String
stationTimeZone: String,
enableAdvancedFeatures: Boolean
},
data () {
return {

View File

@ -9,7 +9,7 @@
<form-source :form="$v.form"></form-source>
<form-schedule :form="$v.form" :schedule-items="form.schedule_items"
:station-time-zone="stationTimeZone"></form-schedule>
<form-advanced :form="$v.form"></form-advanced>
<form-advanced :form="$v.form" v-if="enableAdvancedFeatures"></form-advanced>
</b-tabs>
<invisible-submit-button/>
@ -41,7 +41,8 @@
mixins: [validationMixin],
props: {
createUrl: String,
stationTimeZone: String
stationTimeZone: String,
enableAdvancedFeatures: Boolean
},
data () {
return {

View File

@ -112,6 +112,10 @@ class AppFactory
$settings[Settings::BASE_URL] = $_ENV['BASE_URL'];
}
if (isset($_ENV['ENABLE_ADVANCED_FEATURES'])) {
$settings[Settings::ENABLE_ADVANCED_FEATURES] = $_ENV['ENABLE_ADVANCED_FEATURES'];
}
if (file_exists($settings[Settings::CONFIG_DIR] . '/settings.php')) {
$settingsFile = require($settings[Settings::CONFIG_DIR] . '/settings.php');

View File

@ -1,11 +1,13 @@
<?php
namespace App\Controller\Stations;
use App\Exception\AdvancedFeatureException;
use App\Exception\StationUnsupportedException;
use App\Http\Response;
use App\Http\ServerRequest;
use App\Radio\Backend\Liquidsoap;
use App\Session\Flash;
use App\Settings;
use Doctrine\ORM\EntityManager;
use Psr\Http\Message\ResponseInterface;
@ -14,11 +16,16 @@ class EditLiquidsoapConfigController
public function __invoke(
ServerRequest $request,
Response $response,
EntityManager $em
EntityManager $em,
Settings $settings
): ResponseInterface {
$station = $request->getStation();
$backend = $request->getStationBackend();
if (!$settings->enableAdvancedFeatures()) {
throw new AdvancedFeatureException;
}
if (!($backend instanceof Liquidsoap)) {
throw new StationUnsupportedException;
}

View File

@ -525,6 +525,11 @@ class StationPlaylist
public function getBackendOptions(): array
{
$settings = \App\Settings::getInstance();
if (!$settings->enableAdvancedFeatures()) {
return [];
}
return explode(',', $this->backend_options);
}

View File

@ -5,6 +5,7 @@ use App\Acl;
use App\Entity\User;
use App\Http\Router;
use App\Http\RouterInterface;
use App\Settings;
use Symfony\Contracts\EventDispatcher\Event;
abstract class AbstractBuildMenu extends Event
@ -15,6 +16,8 @@ abstract class AbstractBuildMenu extends Event
protected RouterInterface $router;
protected Settings $settings;
protected array $menu = [];
public function __construct(Acl $acl, User $user, RouterInterface $router)
@ -22,6 +25,7 @@ abstract class AbstractBuildMenu extends Event
$this->acl = $acl;
$this->user = $user;
$this->router = $router;
$this->settings = Settings::getInstance();
}
public function getAcl(): Acl
@ -34,6 +38,11 @@ abstract class AbstractBuildMenu extends Event
return $this->router;
}
public function getSettings(): Settings
{
return $this->settings;
}
/**
* Add a single item to the menu.
*

View File

@ -0,0 +1,18 @@
<?php
namespace App\Exception;
use App\Exception;
use Psr\Log\LogLevel;
use Throwable;
class AdvancedFeatureException extends Exception
{
public function __construct(
string $message = 'This feature is considered "advanced", and advanced features are not currently enabled on this installation. Update your "azuracast.env" file on your host to enable these features.',
int $code = 0,
Throwable $previous = null,
string $loggerLevel = LogLevel::INFO
) {
parent::__construct($message, $code, $previous, $loggerLevel);
}
}

View File

@ -2,10 +2,11 @@
namespace App\Form;
use App\Acl;
use App\Config;
use App\Entity;
use App\Http\ServerRequest;
use App\Radio\Frontend\SHOUTcast;
use App\Config;
use App\Settings;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\ConstraintViolation;
@ -17,7 +18,7 @@ class StationForm extends EntityForm
protected Acl $acl;
protected bool $can_see_administration = false;
protected Settings $settings;
public function __construct(
EntityManager $em,
@ -25,29 +26,44 @@ class StationForm extends EntityForm
ValidatorInterface $validator,
Entity\Repository\StationRepository $station_repo,
Acl $acl,
Config $config
Config $config,
Settings $settings
) {
$form_config = $config->get('forms/station');
parent::__construct($em, $serializer, $validator, $form_config);
$this->acl = $acl;
$this->entityClass = Entity\Station::class;
$this->station_repo = $station_repo;
$this->settings = $settings;
$form_config = $config->get('forms/station');
parent::__construct($em, $serializer, $validator, $form_config);
}
public function canSeeAdministration(): bool
public function configure(array $options): void
{
return $this->can_see_administration;
// Hide "advanced" fields if advanced features are hidden on this installation.
if (!$this->settings->enableAdvancedFeatures()) {
foreach ($options['groups'] as $groupId => $group) {
foreach ($group['elements'] as $elementKey => $element) {
$elementOptions = (array)$element[1];
$class = $elementOptions['label_class'] ?? '';
if (false !== strpos($class, 'advanced')) {
unset($options['groups'][$groupId]['elements'][$elementKey]);
}
}
}
}
parent::configure($options);
}
public function process(ServerRequest $request, $record = null)
{
// Check for administrative permissions and hide admin fields otherwise.
$user = $request->getUser();
$this->can_see_administration = $this->acl->userAllowed($user, Acl::GLOBAL_STATIONS);
if (!$this->can_see_administration) {
$canSeeAdministration = $this->acl->userAllowed($user, Acl::GLOBAL_STATIONS);
if (!$canSeeAdministration) {
foreach ($this->options['groups']['admin']['elements'] as $element_key => $element_info) {
unset($this->fields[$element_key]);
}

View File

@ -78,6 +78,11 @@ class ConfigWriter implements EventSubscriberInterface
return;
}
$appSettings = Settings::getInstance();
if (!$appSettings->enableAdvancedFeatures()) {
return;
}
$station = $event->getStation();
$settings = $station->getBackendConfig();

View File

@ -32,6 +32,8 @@ class Settings extends Collection
public const DOCKER_REVISION = 'docker_revision';
public const ENABLE_ADVANCED_FEATURES = 'enable_advanced_features';
// Default settings
protected array $data = [
self::APP_NAME => 'Application',
@ -139,4 +141,13 @@ class Settings extends Collection
$compareVersion = (int)$this->get(self::DOCKER_REVISION, 0);
return ($compareVersion >= $version);
}
public function enableAdvancedFeatures(): bool
{
if (!$this->isDocker()) {
return true;
}
return (bool)($this->data[self::ENABLE_ADVANCED_FEATURES] ?? true);
}
}

View File

@ -5,17 +5,18 @@ $props = [
'locale' => substr($customization->getLocale(), 0, 2),
'filesUrl' => $router->fromHere('stations:files:index'),
'stationTimeZone' => $station_tz,
'enableAdvancedFeatures' => $settings->enableAdvancedFeatures(),
]
?>
var station_playlists;
$(function () {
station_playlists = new Vue({
el: '#station-playlist',
render: function (createElement) {
return createElement(StationPlaylist.default, {
props: <?=json_encode($props) ?>
});
}
});
station_playlists = new Vue({
el: '#station-playlist',
render: function (createElement) {
return createElement(StationPlaylist.default, {
props: <?=json_encode($props) ?>
});
}
});
});

View File

@ -52,7 +52,7 @@
"dist/radio_player.js": "dist/radio_player-562bd0b7db.js",
"dist/station_media.js": "dist/station_media-122102da52.js",
"dist/station_on_demand.js": "dist/station_on_demand-83b1d61083.js",
"dist/station_playlists.js": "dist/station_playlists-aa3e568663.js",
"dist/station_playlists.js": "dist/station_playlists-821b66e79c.js",
"dist/station_streamers.js": "dist/station_streamers-b57da17301.js",
"dist/vue_gettext.js": "dist/vue_gettext-2d25ba9686.js",
"dist/webcaster.js": "dist/webcaster-09a0e0221a.js",

File diff suppressed because one or more lines are too long