From 8d8a037e3fe9b1d2ccbc4169ce59b13000b59cb0 Mon Sep 17 00:00:00 2001 From: Jelle Besseling Date: Tue, 12 Oct 2021 13:33:44 +0200 Subject: Allow configuration to be static/readonly (#4315) * Allow configuration to be static/readonly * Make all components disableable * Improve disabled component styling * Rename edits allowed field in configuration * Fix CI --- server/controllers/api/config.ts | 4 +- server/initializers/config.ts | 31 ++- server/lib/server-config-manager.ts | 1 + server/middlewares/validators/config.ts | 16 +- server/tests/api/server/config.ts | 364 +++++++++++++++++--------------- 5 files changed, 234 insertions(+), 182 deletions(-) (limited to 'server') diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts index d542f62aa..5ea1f67c9 100644 --- a/server/controllers/api/config.ts +++ b/server/controllers/api/config.ts @@ -11,7 +11,7 @@ import { objectConverter } from '../../helpers/core-utils' import { CONFIG, reloadConfig } from '../../initializers/config' import { ClientHtml } from '../../lib/client-html' import { asyncMiddleware, authenticate, ensureUserHasRight, openapiOperationDoc } from '../../middlewares' -import { customConfigUpdateValidator } from '../../middlewares/validators/config' +import { customConfigUpdateValidator, ensureConfigIsEditable } from '../../middlewares/validators/config' const configRouter = express.Router() @@ -38,6 +38,7 @@ configRouter.put('/custom', openapiOperationDoc({ operationId: 'putCustomConfig' }), authenticate, ensureUserHasRight(UserRight.MANAGE_CONFIGURATION), + ensureConfigIsEditable, customConfigUpdateValidator, asyncMiddleware(updateCustomConfig) ) @@ -46,6 +47,7 @@ configRouter.delete('/custom', openapiOperationDoc({ operationId: 'delCustomConfig' }), authenticate, ensureUserHasRight(UserRight.MANAGE_CONFIGURATION), + ensureConfigIsEditable, asyncMiddleware(deleteCustomConfig) ) diff --git a/server/initializers/config.ts b/server/initializers/config.ts index be9fc61f0..b2a8e9e19 100644 --- a/server/initializers/config.ts +++ b/server/initializers/config.ts @@ -195,6 +195,13 @@ const CONFIG = { URL: config.get('peertube.check_latest_version.url') } }, + WEBADMIN: { + CONFIGURATION: { + EDITS: { + ALLOWED: config.get('webadmin.configuration.edit.allowed') + } + } + }, ADMIN: { get EMAIL () { return config.get('admin.email') } }, @@ -411,14 +418,22 @@ export { // --------------------------------------------------------------------------- function getLocalConfigFilePath () { - const configSources = config.util.getConfigSources() - if (configSources.length === 0) throw new Error('Invalid config source.') + const localConfigDir = getLocalConfigDir() let filename = 'local' if (process.env.NODE_ENV) filename += `-${process.env.NODE_ENV}` if (process.env.NODE_APP_INSTANCE) filename += `-${process.env.NODE_APP_INSTANCE}` - return join(dirname(configSources[0].name), filename + '.json') + return join(localConfigDir, filename + '.json') +} + +function getLocalConfigDir () { + if (process.env.PEERTUBE_LOCAL_CONFIG) return process.env.PEERTUBE_LOCAL_CONFIG + + const configSources = config.util.getConfigSources() + if (configSources.length === 0) throw new Error('Invalid config source.') + + return dirname(configSources[0].name) } function buildVideosRedundancy (objs: any[]): VideosRedundancyStrategy[] { @@ -437,19 +452,19 @@ function buildVideosRedundancy (objs: any[]): VideosRedundancyStrategy[] { export function reloadConfig () { - function getConfigDirectory () { + function getConfigDirectories () { if (process.env.NODE_CONFIG_DIR) { - return process.env.NODE_CONFIG_DIR + return process.env.NODE_CONFIG_DIR.split(":") } - return join(root(), 'config') + return [ join(root(), 'config') ] } function purge () { - const directory = getConfigDirectory() + const directories = getConfigDirectories() for (const fileName in require.cache) { - if (fileName.includes(directory) === false) { + if (directories.some((dir) => fileName.includes(dir)) === false) { continue } diff --git a/server/lib/server-config-manager.ts b/server/lib/server-config-manager.ts index 80d87a9d3..358f47133 100644 --- a/server/lib/server-config-manager.ts +++ b/server/lib/server-config-manager.ts @@ -42,6 +42,7 @@ class ServerConfigManager { const defaultTheme = getThemeOrDefault(CONFIG.THEME.DEFAULT, DEFAULT_THEME_NAME) return { + allowEdits: CONFIG.WEBADMIN.CONFIGURATION.EDITS.ALLOWED, instance: { name: CONFIG.INSTANCE.NAME, shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION, diff --git a/server/middlewares/validators/config.ts b/server/middlewares/validators/config.ts index 16a840667..5f1ac89bc 100644 --- a/server/middlewares/validators/config.ts +++ b/server/middlewares/validators/config.ts @@ -1,13 +1,14 @@ import express from 'express' import { body } from 'express-validator' import { isIntOrNull } from '@server/helpers/custom-validators/misc' -import { isEmailEnabled } from '@server/initializers/config' +import { CONFIG, isEmailEnabled } from '@server/initializers/config' import { CustomConfig } from '../../../shared/models/server/custom-config.model' import { isThemeNameValid } from '../../helpers/custom-validators/plugins' import { isUserNSFWPolicyValid, isUserVideoQuotaDailyValid, isUserVideoQuotaValid } from '../../helpers/custom-validators/users' import { logger } from '../../helpers/logger' import { isThemeRegistered } from '../../lib/plugins/theme-utils' import { areValidationErrors } from './shared' +import { HttpStatusCode } from '@shared/models/http/http-error-codes' const customConfigUpdateValidator = [ body('instance.name').exists().withMessage('Should have a valid instance name'), @@ -104,10 +105,21 @@ const customConfigUpdateValidator = [ } ] +function ensureConfigIsEditable (req: express.Request, res: express.Response, next: express.NextFunction) { + if (!CONFIG.WEBADMIN.CONFIGURATION.EDITS.ALLOWED) { + return res.fail({ + status: HttpStatusCode.METHOD_NOT_ALLOWED_405, + message: 'Server configuration is static and cannot be edited' + }) + } + return next() +} + // --------------------------------------------------------------------------- export { - customConfigUpdateValidator + customConfigUpdateValidator, + ensureConfigIsEditable } function checkInvalidConfigIfEmailDisabled (customConfig: CustomConfig, res: express.Response) { diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts index c4dd882b8..e057ec1a2 100644 --- a/server/tests/api/server/config.ts +++ b/server/tests/api/server/config.ts @@ -201,6 +201,199 @@ function checkUpdatedConfig (data: CustomConfig) { expect(data.broadcastMessage.dismissable).to.be.true } +const newCustomConfig: CustomConfig = { + instance: { + name: 'PeerTube updated', + shortDescription: 'my short description', + description: 'my super description', + terms: 'my super terms', + codeOfConduct: 'my super coc', + + creationReason: 'my super creation reason', + moderationInformation: 'my super moderation information', + administrator: 'Kuja', + maintenanceLifetime: 'forever', + businessModel: 'my super business model', + hardwareInformation: '2vCore 3GB RAM', + + languages: [ 'en', 'es' ], + categories: [ 1, 2 ], + + isNSFW: true, + defaultNSFWPolicy: 'blur' as 'blur', + + defaultClientRoute: '/videos/recently-added', + + customizations: { + javascript: 'alert("coucou")', + css: 'body { background-color: red; }' + } + }, + theme: { + default: 'default' + }, + services: { + twitter: { + username: '@Kuja', + whitelisted: true + } + }, + cache: { + previews: { + size: 2 + }, + captions: { + size: 3 + }, + torrents: { + size: 4 + } + }, + signup: { + enabled: false, + limit: 5, + requiresEmailVerification: false, + minimumAge: 10 + }, + admin: { + email: 'superadmin1@example.com' + }, + contactForm: { + enabled: false + }, + user: { + videoQuota: 5242881, + videoQuotaDaily: 318742 + }, + transcoding: { + enabled: true, + allowAdditionalExtensions: true, + allowAudioFiles: true, + threads: 1, + concurrency: 3, + profile: 'vod_profile', + resolutions: { + '0p': false, + '240p': false, + '360p': true, + '480p': true, + '720p': false, + '1080p': false, + '1440p': false, + '2160p': false + }, + webtorrent: { + enabled: true + }, + hls: { + enabled: false + } + }, + live: { + enabled: true, + allowReplay: true, + maxDuration: 5000, + maxInstanceLives: -1, + maxUserLives: 10, + transcoding: { + enabled: true, + threads: 4, + profile: 'live_profile', + resolutions: { + '240p': true, + '360p': true, + '480p': true, + '720p': true, + '1080p': true, + '1440p': true, + '2160p': true + } + } + }, + import: { + videos: { + concurrency: 4, + http: { + enabled: false + }, + torrent: { + enabled: false + } + } + }, + trending: { + videos: { + algorithms: { + enabled: [ 'best', 'hot', 'most-viewed', 'most-liked' ], + default: 'hot' + } + } + }, + autoBlacklist: { + videos: { + ofUsers: { + enabled: true + } + } + }, + followers: { + instance: { + enabled: false, + manualApproval: true + } + }, + followings: { + instance: { + autoFollowBack: { + enabled: true + }, + autoFollowIndex: { + enabled: true, + indexUrl: 'https://updated.example.com' + } + } + }, + broadcastMessage: { + enabled: true, + level: 'error', + message: 'super bad message', + dismissable: true + }, + search: { + remoteUri: { + anonymous: true, + users: true + }, + searchIndex: { + enabled: true, + url: 'https://search.joinpeertube.org', + disableLocalSearch: true, + isDefaultSearch: true + } + } +} + +describe('Test static config', function () { + let server: PeerTubeServer = null + + before(async function () { + this.timeout(30000) + + server = await createSingleServer(1, { webadmin: { configuration: { edit: { allowed: false } } } }) + await setAccessTokensToServers([ server ]) + }) + + it('Should tell the client that edits are not allowed', async function () { + const data = await server.config.getConfig() + + expect(data.allowEdits).to.be.false + }) + + it('Should error when client tries to update', async function () { + await server.config.updateCustomConfig({ newCustomConfig, expectedStatus: 405 }) + }) +}) + describe('Test config', function () { let server: PeerTubeServer = null @@ -252,177 +445,6 @@ describe('Test config', function () { }) it('Should update the customized configuration', async function () { - const newCustomConfig: CustomConfig = { - instance: { - name: 'PeerTube updated', - shortDescription: 'my short description', - description: 'my super description', - terms: 'my super terms', - codeOfConduct: 'my super coc', - - creationReason: 'my super creation reason', - moderationInformation: 'my super moderation information', - administrator: 'Kuja', - maintenanceLifetime: 'forever', - businessModel: 'my super business model', - hardwareInformation: '2vCore 3GB RAM', - - languages: [ 'en', 'es' ], - categories: [ 1, 2 ], - - isNSFW: true, - defaultNSFWPolicy: 'blur' as 'blur', - - defaultClientRoute: '/videos/recently-added', - - customizations: { - javascript: 'alert("coucou")', - css: 'body { background-color: red; }' - } - }, - theme: { - default: 'default' - }, - services: { - twitter: { - username: '@Kuja', - whitelisted: true - } - }, - cache: { - previews: { - size: 2 - }, - captions: { - size: 3 - }, - torrents: { - size: 4 - } - }, - signup: { - enabled: false, - limit: 5, - requiresEmailVerification: false, - minimumAge: 10 - }, - admin: { - email: 'superadmin1@example.com' - }, - contactForm: { - enabled: false - }, - user: { - videoQuota: 5242881, - videoQuotaDaily: 318742 - }, - transcoding: { - enabled: true, - allowAdditionalExtensions: true, - allowAudioFiles: true, - threads: 1, - concurrency: 3, - profile: 'vod_profile', - resolutions: { - '0p': false, - '240p': false, - '360p': true, - '480p': true, - '720p': false, - '1080p': false, - '1440p': false, - '2160p': false - }, - webtorrent: { - enabled: true - }, - hls: { - enabled: false - } - }, - live: { - enabled: true, - allowReplay: true, - maxDuration: 5000, - maxInstanceLives: -1, - maxUserLives: 10, - transcoding: { - enabled: true, - threads: 4, - profile: 'live_profile', - resolutions: { - '240p': true, - '360p': true, - '480p': true, - '720p': true, - '1080p': true, - '1440p': true, - '2160p': true - } - } - }, - import: { - videos: { - concurrency: 4, - http: { - enabled: false - }, - torrent: { - enabled: false - } - } - }, - trending: { - videos: { - algorithms: { - enabled: [ 'best', 'hot', 'most-viewed', 'most-liked' ], - default: 'hot' - } - } - }, - autoBlacklist: { - videos: { - ofUsers: { - enabled: true - } - } - }, - followers: { - instance: { - enabled: false, - manualApproval: true - } - }, - followings: { - instance: { - autoFollowBack: { - enabled: true - }, - autoFollowIndex: { - enabled: true, - indexUrl: 'https://updated.example.com' - } - } - }, - broadcastMessage: { - enabled: true, - level: 'error', - message: 'super bad message', - dismissable: true - }, - search: { - remoteUri: { - anonymous: true, - users: true - }, - searchIndex: { - enabled: true, - url: 'https://search.joinpeertube.org', - disableLocalSearch: true, - isDefaultSearch: true - } - } - } await server.config.updateCustomConfig({ newCustomConfig }) const data = await server.config.getCustomConfig() -- cgit v1.2.3