feat: replace exploded base_* with public_url

Fixes #1574

BREAKING CHANGE: The `general` section in the config schema has changed: the `general.base_*`, `general.protocol` and `general.force_ssl` configuration fields have been replaced with a single `general.public_url` field. Be sure to use a valid url with the new configuration field.
This commit is contained in:
jo 2022-04-17 14:38:49 +02:00 committed by Kyle Robbertze
parent d020fbd983
commit 751d430bcc
13 changed files with 297 additions and 144 deletions

View File

@ -11,10 +11,8 @@ def config_filepath(tmp_path: Path):
filepath.write_text(
"""
[general]
public_url = http://localhost/test
api_key = TEST_KEY
base_dir = /test
base_port = 80
base_url = localhost
"""
)
return filepath

View File

@ -1,6 +1,7 @@
import os
os.environ.setdefault("LIBRETIME_DEBUG", "true")
os.environ.setdefault("LIBRETIME_GENERAL_PUBLIC_URL", "http://localhost")
os.environ.setdefault("LIBRETIME_GENERAL_API_KEY", "testing")
# pylint: disable=wrong-import-position,unused-import

View File

@ -11,21 +11,11 @@ The `general` section configure anything related to the legacy and API services.
```ini
[general]
# The internal API authentication key, this field is required
# The public url. This field is REQUIRED
public_url = https://example.com
# The internal API authentication key, this field is REQUIRED
api_key = some_random_generated_secret!
# The public url scheme
# Mutually exclusive with force_ssl
protocol =
# The public url hostname, default is localhost
base_url = localhost
# The public url port
base_port =
# The public url base path, default is /
base_dir = /
# Force https for generated urls, default is false
force_ssl = false
# How many hours ahead Playout should cache scheduled media files, default is 1
cache_ahead_hours = 1

View File

@ -46,6 +46,14 @@ sudo apt purge \
uwsgi-plugin-python3
```
### New configuration schema
The configuration schema was updated.
The `general` section has been changed:
- the `general.protocol`, `general.base_url`, `general.base_port`, `general.base_dir` and `general.force_ssl` entries were replaced with a single `general.public_url` entry, be sure to use a valid url with the new configuration entry.
## :warning: Known issues
The following issues may need a workaround for the time being. Please search the [issues](https://github.com/libretime/libretime/issues) before reporting problems not listed below.

View File

@ -1,5 +1,7 @@
<?php
use League\Uri\Uri;
class Application_Common_HTTPHelper
{
/**
@ -30,11 +32,16 @@ class Application_Common_HTTPHelper
public static function getStationUrl($secured = true)
{
$CC_CONFIG = Config::getConfig();
$baseUrl = $CC_CONFIG['baseUrl'];
$baseDir = $CC_CONFIG['baseDir'];
$basePort = $CC_CONFIG['basePort'];
$forceSSL = $CC_CONFIG['forceSSL'];
$configProtocol = $CC_CONFIG['protocol'];
$url = Uri::createFromComponents(
[
'scheme' => $CC_CONFIG['protocol'],
'host' => $CC_CONFIG['baseUrl'],
'port' => $CC_CONFIG['basePort'],
'path' => $CC_CONFIG['baseDir'],
]
);
if (empty($baseDir)) {
$baseDir = '/';
}
@ -45,26 +52,7 @@ class Application_Common_HTTPHelper
$baseDir = $baseDir . '/';
}
// Set in reverse order of preference. ForceSSL configuration takes absolute preference, then
// the protocol set in config. If neither are set, the port is used to determine the scheme
$scheme = 'http';
if ($secured && $basePort == '443') {
$scheme = 'https';
}
if (!empty($configProtocol)) {
$scheme = $configProtocol;
}
if ($forceSSL) {
$scheme = 'https';
}
$portStr = '';
if (($scheme == 'http' && $basePort !== '80')
|| ($scheme == 'https' && $basePort !== '443')) {
$portStr = ":{$basePort}";
}
return "{$scheme}://{$baseUrl}{$portStr}{$baseDir}";
return rtrim($url, '/') . '/';
}
/**
@ -114,7 +102,7 @@ class ZendActionHttpException extends Exception
Exception $previous = null
) {
Logging::error('Error in action ' . $action->getRequest()->getActionName()
. " with status code {$statusCode}: {$message}");
. " with status code {$statusCode}: {$message}");
$action->getResponse()
->setHttpResponseCode($statusCode)
->appendBody($message);

View File

@ -1,6 +1,8 @@
<?php
// THIS FILE IS NOT MEANT FOR CUSTOMIZING.
use League\Uri\Contracts\UriException;
use League\Uri\Uri;
class Config
{
@ -17,12 +19,24 @@ class Config
// //////////////////////////////////////////////////////////////////////////////
$CC_CONFIG['apiKey'] = [$values['general']['api_key']];
// Base URL
$CC_CONFIG['protocol'] = $values['general']['protocol'] ?? '';
$CC_CONFIG['baseDir'] = $values['general']['base_dir'];
$CC_CONFIG['baseUrl'] = $values['general']['base_url'];
$CC_CONFIG['basePort'] = $values['general']['base_port'];
$CC_CONFIG['forceSSL'] = Config::isYesValue($values['general']['force_ssl'] ?? false);
// Explode public_url into multiple component with possible defaults for required fields
try {
$public_url = Uri::createFromString($values['general']['public_url']);
} catch (UriException $e) {
echo 'could not parse configuration field general.public_url: ' . $e->getMessage();
exit;
}
$scheme = $public_url->getScheme() ?? 'http';
$host = $public_url->getHost() ?? 'localhost';
$port = $public_url->getPort() ?? ($scheme == 'https' ? 443 : 80);
$path = rtrim($public_url->getPath() ?? '', '/') . '/'; // Path requires a trailing slash
$CC_CONFIG['protocol'] = $scheme;
$CC_CONFIG['baseUrl'] = $host;
$CC_CONFIG['basePort'] = $port;
$CC_CONFIG['baseDir'] = $path;
$CC_CONFIG['dev_env'] = $values['general']['dev_env'] ?? 'production';
$CC_CONFIG['auth'] = $values['general']['auth'] ?? 'local';

View File

@ -3,52 +3,40 @@
<form action="#" role="form" id="generalSettingsForm">
<h3 class="form-title">General Settings</h3>
<p>
These values are automatically pulled from your webserver settings, under most circumstances you will not need to change them.
</p>
<span id="helpBlock" class="help-block help-message"></span>
<div id="generalFormBody">
<div class="form-group">
<label class="control-label" for="generalHost">Webserver Host</label>
<input required class="form-control" type="text" name="generalHost" id="generalHost" placeholder="Host" value="<?php echo $_SERVER['SERVER_NAME']; ?>"/>
<label class="control-label" for="publicUrl">Public URL</label>
<input required class="form-control" type="text" name="publicUrl" id="publicUrl" placeholder="https://example.com/" />
<span class="glyphicon glyphicon-remove form-control-feedback"></span>
</div>
<div class="form-group">
<label class="control-label" for="generalPort">Webserver Port</label>
<input required class="form-control" type="text" name="generalPort" id="generalPort" placeholder="Port" value="<?php echo $_SERVER['SERVER_PORT']; ?>"/>
<span class="glyphicon glyphicon-remove form-control-feedback"></span>
</div>
<input class="form-control" type="hidden" name="generalErr" id="generalErr" aria-describedby="helpBlock"/>
<p>The CORS URL can be setup during install if you are accessing your LibreTime instance behind a Proxy.
<input class="form-control" type="hidden" name="generalErr" id="generalErr" aria-describedby="helpBlock" />
<hr />
<p>
The CORS URL can be setup during install if you are accessing your LibreTime instance behind a Proxy.
This is common with docker setups. If you have a reverse proxy setup enter the URL below, otherwise you
can safely ignore this. Please enter one URL per line. Include the entire URL such as http://example.com
can safely ignore this. Please enter one URL per line. Include the entire URL such as http://example.com
If you are reinstalling LibreTime on an existing setup you can ignore this as well,
the settings in your existing database will be retained unless you enter new values below.
</p>
<div id="corsSlideToggle">
<span><strong>CORS URL </strong></span><span id="corsCaret" class="caret"></span><hr/>
</div>
<div id="corsFormBody">
<div class="form-group">
<label class="control-label" for="corsUrl">CORS URLs</label>
<textarea name="corsUrl" class="form-control" id="corsUrl" rows="4" cols="50"></textarea>
</div>
<label class="control-label" for="corsUrl">CORS URLs</label>
<textarea name="corsUrl" class="form-control" id="corsUrl" rows="4" cols="50"></textarea>
</div>
</div>
<div>
<input type="submit" formtarget="generalSettingsForm" class="btn btn-primary btn-next" value="Next &#10097;"/>
<input type="button" class="btn btn-primary btn-back" value="&#10096; Back"/>
<input type="submit" formtarget="generalSettingsForm" class="btn btn-primary btn-next" value="Next &#10097;" />
<input type="button" class="btn btn-primary btn-back" value="&#10096; Back" />
</div>
</form>
<script>
$("#publicUrl").val(function() {
return window.location.href;
});
$("#corsUrl").text(function() {
return window.location.origin;
});
$("#corsSlideToggle").click(function() {
$("#corsFormBody").slideToggle(500);
$("#corsCaret").toggleClass("caret-up");
});
$("#generalSettingsForm").submit(function(e) {
submitForm(e, "GeneralSetup");
});

View File

@ -1,24 +1,14 @@
# This is an example configuration file.
# See https://libretime.org/docs/setup/configuration
#
# ----------------------------------------------------------------------
# G E N E R A L S E T T I N G S
# ----------------------------------------------------------------------
[general]
# The internal API authentication key, this field is required
# The public url, this field is REQUIRED
public_url =
# The internal API authentication key, this field is REQUIRED
api_key =
# The public url scheme
# Mutually exclusive with force_ssl
protocol = http
# The public url hostname, default is localhost
base_url = localhost
# The public url port
base_port = 80
# The public url base path, default is /
base_dir = /
# Force https for generated urls, default is false
force_ssl = false
# How many hours ahead Playout should cache scheduled media files, default is 1
cache_ahead_hours = 1

View File

@ -25,6 +25,7 @@
"james-heinrich/getid3": "^1.9",
"jooola/celery-php": "dev-master",
"jooola/propel1": "dev-master",
"league/uri": "6.4.0",
"php-amqplib/php-amqplib": "^3.0",
"simplepie/simplepie": "^1.5",
"zf1s/zend-acl": "^1.13",

222
legacy/composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "6ff6e427d89556d1633e2b49764de2c4",
"content-hash": "a7659135ed373ebf5f68c9de86891e0b",
"packages": [
{
"name": "composer/semver",
@ -305,6 +305,173 @@
},
"time": "2022-01-13T02:39:05+00:00"
},
{
"name": "league/uri",
"version": "6.4.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/uri.git",
"reference": "09da64118eaf4c5d52f9923a1e6a5be1da52fd9a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/uri/zipball/09da64118eaf4c5d52f9923a1e6a5be1da52fd9a",
"reference": "09da64118eaf4c5d52f9923a1e6a5be1da52fd9a",
"shasum": ""
},
"require": {
"ext-json": "*",
"league/uri-interfaces": "^2.1",
"php": ">=7.2",
"psr/http-message": "^1.0"
},
"conflict": {
"league/uri-schemes": "^1.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.16",
"phpstan/phpstan": "^0.12",
"phpstan/phpstan-phpunit": "^0.12",
"phpstan/phpstan-strict-rules": "^0.12",
"phpunit/phpunit": "^8.0 || ^9.0",
"psr/http-factory": "^1.0"
},
"suggest": {
"ext-fileinfo": "Needed to create Data URI from a filepath",
"ext-intl": "Needed to improve host validation",
"league/uri-components": "Needed to easily manipulate URI objects",
"psr/http-factory": "Needed to use the URI factory"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "6.x-dev"
}
},
"autoload": {
"psr-4": {
"League\\Uri\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ignace Nyamagana Butera",
"email": "nyamsprod@gmail.com",
"homepage": "https://nyamsprod.com"
}
],
"description": "URI manipulation library",
"homepage": "http://uri.thephpleague.com",
"keywords": [
"data-uri",
"file-uri",
"ftp",
"hostname",
"http",
"https",
"middleware",
"parse_str",
"parse_url",
"psr-7",
"query-string",
"querystring",
"rfc3986",
"rfc3987",
"rfc6570",
"uri",
"uri-template",
"url",
"ws"
],
"support": {
"docs": "https://uri.thephpleague.com",
"forum": "https://thephpleague.slack.com",
"issues": "https://github.com/thephpleague/uri/issues",
"source": "https://github.com/thephpleague/uri/tree/6.4.0"
},
"funding": [
{
"url": "https://github.com/sponsors/nyamsprod",
"type": "github"
}
],
"time": "2020-11-22T14:29:11+00:00"
},
{
"name": "league/uri-interfaces",
"version": "2.3.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/uri-interfaces.git",
"reference": "00e7e2943f76d8cb50c7dfdc2f6dee356e15e383"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/00e7e2943f76d8cb50c7dfdc2f6dee356e15e383",
"reference": "00e7e2943f76d8cb50c7dfdc2f6dee356e15e383",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": "^7.2 || ^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.19",
"phpstan/phpstan": "^0.12.90",
"phpstan/phpstan-phpunit": "^0.12.19",
"phpstan/phpstan-strict-rules": "^0.12.9",
"phpunit/phpunit": "^8.5.15 || ^9.5"
},
"suggest": {
"ext-intl": "to use the IDNA feature",
"symfony/intl": "to use the IDNA feature via Symfony Polyfill"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
}
},
"autoload": {
"psr-4": {
"League\\Uri\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ignace Nyamagana Butera",
"email": "nyamsprod@gmail.com",
"homepage": "https://nyamsprod.com"
}
],
"description": "Common interface for URI representation",
"homepage": "http://github.com/thephpleague/uri-interfaces",
"keywords": [
"rfc3986",
"rfc3987",
"uri",
"url"
],
"support": {
"issues": "https://github.com/thephpleague/uri-interfaces/issues",
"source": "https://github.com/thephpleague/uri-interfaces/tree/2.3.0"
},
"funding": [
{
"url": "https://github.com/sponsors/nyamsprod",
"type": "github"
}
],
"time": "2021-06-28T04:27:21+00:00"
},
{
"name": "paragonie/constant_time_encoding",
"version": "v2.5.0",
@ -726,6 +893,59 @@
],
"time": "2022-01-30T08:50:05+00:00"
},
{
"name": "psr/http-message",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"support": {
"source": "https://github.com/php-fig/http-message/tree/master"
},
"time": "2016-08-06T14:39:51+00:00"
},
{
"name": "simplepie/simplepie",
"version": "1.6.0",

View File

@ -17,8 +17,7 @@ class GeneralSetup extends Setup
protected static $_properties;
// Constant form field names for passing errors back to the front-end
public const GENERAL_PORT = 'generalPort';
public const GENERAL_HOST = 'generalHost';
public const PUBLIC_URL = 'publicUrl';
public const CORS_URL = 'corsUrl';
public static $cors_url;
@ -31,8 +30,7 @@ class GeneralSetup extends Setup
{
self::$_properties = [
'api_key' => $this->generateRandomString(),
'base_url' => $settings[self::GENERAL_HOST],
'base_port' => $settings[self::GENERAL_PORT],
'public_url' => $settings[self::PUBLIC_URL],
'cors_url' => $settings[self::CORS_URL],
];
self::$cors_url = $settings[self::CORS_URL];

View File

@ -1,3 +1,8 @@
[general]
dev_env = testing
public_url = http://localhost
api_key = H2NRICX6CM8F50CU123C
[database]
host = localhost
port = 5432
@ -11,33 +16,3 @@ port = 5672
user = airtime_tests
password = airtime_tests
vhost = /airtime_tests
[general]
dev_env = testing
api_key = H2NRICX6CM8F50CU123C
airtime_dir = /usr/share/airtime
base_url = localhost
base_port = 80
base_dir = /
cache_ahead_hours = 1
station_id = teststation
[current_backend]
storage_backend=file
[monit]
monit_user = guest
monit_password = airtime
[ldap]
hostname = ldap.example.org
binddn = 'uid=libretime,cn=sysaccounts,cn=etc,dc=int,dc=example,dc=org'
password = hackme
account_domain = INT.EXAMPLE.ORG
basedn = 'cn=users,cn=accounts,dc=int,dc=example,dc=org'
filter_field = uid
groupmap_guest = 'cn=guest,cn=groups,cn=accounts,dc=int,dc=example,dc=org'
groupmap_host = 'cn=host,cn=groups,cn=accounts,dc=int,dc=example,dc=org'
groupmap_program_manager = 'cn=program_manager,cn=groups,cn=accounts,dc=int,dc=example,dc=org'
groupmap_admin = 'cn=admins,cn=groups,cn=accounts,dc=int,dc=example,dc=org'
groupmap_superadmin = 'cn=superadmin,cn=groups,cn=accounts,dc=int,dc=example,dc=org'

View File

@ -3,12 +3,11 @@ from configparser import ConfigParser
from os import environ
from pathlib import Path
from typing import Any, Dict, Optional, Union
from urllib.parse import urlunsplit
from loguru import logger
# pylint: disable=no-name-in-module
from pydantic import BaseModel, ValidationError
from pydantic import AnyHttpUrl, BaseModel, ValidationError
from pydantic.fields import ModelField
from pydantic.utils import deep_update
from yaml import YAMLError, safe_load
@ -108,26 +107,9 @@ class BaseConfig(BaseModel):
# pylint: disable=too-few-public-methods
class GeneralConfig(BaseModel):
public_url: AnyHttpUrl
api_key: str
protocol: str = "http"
base_url: str = "localhost"
base_port: Optional[int]
base_dir: str = "/"
force_ssl: bool = False
@property
def public_url(self) -> str:
scheme = "https" if self.force_ssl else self.protocol
location = self.base_url
if self.base_port is not None:
location += f":{self.base_port}"
path = self.base_dir.rstrip("/")
return urlunsplit((scheme, location, path, None, None))
# pylint: disable=too-few-public-methods
class DatabaseConfig(BaseModel):