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:
parent
ba0d38af6c
commit
0069df6d2d
|
@ -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" \
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#
|
||||
|
||||
APPLICATION_ENV=development
|
||||
ENABLE_ADVANCED_FEATURES=true
|
||||
COMPOSER_PLUGIN_MODE=false
|
||||
|
||||
# Developer options.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
[
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -525,6 +525,11 @@ class StationPlaylist
|
|||
|
||||
public function getBackendOptions(): array
|
||||
{
|
||||
$settings = \App\Settings::getInstance();
|
||||
if (!$settings->enableAdvancedFeatures()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return explode(',', $this->backend_options);
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
@ -78,6 +78,11 @@ class ConfigWriter implements EventSubscriberInterface
|
|||
return;
|
||||
}
|
||||
|
||||
$appSettings = Settings::getInstance();
|
||||
if (!$appSettings->enableAdvancedFeatures()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$station = $event->getStation();
|
||||
$settings = $station->getBackendConfig();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) ?>
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
|
@ -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
Loading…
Reference in New Issue