mirror of
https://github.com/BookStackApp/BookStack.git
synced 2026-07-15 21:31:36 +03:00
Compare commits
9 Commits
v26.03.1
...
Zhey-on/fe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e42fda893c | ||
|
|
25790fd024 | ||
|
|
1763ac550b | ||
|
|
fd6867e577 | ||
|
|
5ebc1fe3b0 | ||
|
|
a44756168d | ||
|
|
fa1dc162bd | ||
|
|
5763d26b17 | ||
|
|
04dd9f8e19 |
@@ -395,6 +395,18 @@ ALLOWED_IFRAME_HOSTS=null
|
||||
# Current host and source for the "DRAWIO" setting will be auto-appended to the sources configured.
|
||||
ALLOWED_IFRAME_SOURCES="https://*.draw.io https://*.youtube.com https://*.youtube-nocookie.com https://*.vimeo.com"
|
||||
|
||||
# A list of sources/hostnames that can be loaded as CSS styles within BookStack.
|
||||
# Space separated if multiple. BookStack host domain is auto-inferred.
|
||||
# Defaults to a permissive set if not provided.
|
||||
# Example: ALLOWED_CSS_SOURCES="https://fonts.googleapis.com"
|
||||
ALLOWED_CSS_SOURCES=null
|
||||
|
||||
# A list of sources/hostnames that can be loaded as image content within BookStack.
|
||||
# Space separated if multiple. BookStack host domain is auto-inferred.
|
||||
# Defaults to a permissive set if not provided.
|
||||
# Example: ALLOWED_IMAGE_SOURCES="https://images.example.com data:"
|
||||
ALLOWED_IMAGE_SOURCES=null
|
||||
|
||||
# A list of the sources/hostnames that can be reached by application SSR calls.
|
||||
# This is used wherever users can provide URLs/hosts in-platform, like for webhooks.
|
||||
# Host-specific functionality (usually controlled via other options) like auth
|
||||
|
||||
1
.github/translators.txt
vendored
1
.github/translators.txt
vendored
@@ -533,3 +533,4 @@ JanDziaslo :: Polish
|
||||
Charllys Fernandes (CharllysFernandes) :: Portuguese, Brazilian
|
||||
Ilgiz Zigangirov (inov8) :: Russian
|
||||
Max Israelsson (Blezie) :: Swedish
|
||||
Skiddybison5924 (chris-devel0per) :: German
|
||||
|
||||
@@ -48,8 +48,7 @@ class RegisterController extends Controller
|
||||
public function postRegister(Request $request)
|
||||
{
|
||||
$this->registrationService->ensureRegistrationAllowed();
|
||||
$this->validator($request->all())->validate();
|
||||
$userData = $request->all();
|
||||
$userData = $this->validator($request->all())->validate();
|
||||
|
||||
try {
|
||||
$user = $this->registrationService->registerUser($userData);
|
||||
|
||||
@@ -83,7 +83,7 @@ class RegistrationService
|
||||
// Email restriction
|
||||
$this->ensureEmailDomainAllowed($userEmail);
|
||||
|
||||
// Ensure user does not already exist
|
||||
// Ensure the user does not already exist
|
||||
$alreadyUser = !is_null($this->userRepo->getByEmail($userEmail));
|
||||
if ($alreadyUser) {
|
||||
throw new UserRegistrationException(trans('errors.error_user_exists_different_creds', ['email' => $userEmail]), '/login');
|
||||
@@ -99,7 +99,7 @@ class RegistrationService
|
||||
$newUser = $this->userRepo->createWithoutActivity($userData, $emailConfirmed);
|
||||
$newUser->attachDefaultRole();
|
||||
|
||||
// Assign social account if given
|
||||
// Assign a social account if given
|
||||
if ($socialAccount) {
|
||||
$newUser->socialAccounts()->save($socialAccount);
|
||||
}
|
||||
@@ -107,7 +107,7 @@ class RegistrationService
|
||||
Activity::add(ActivityType::AUTH_REGISTER, $socialAccount ?? $newUser);
|
||||
Theme::dispatch(ThemeEvents::AUTH_REGISTER, $authSystem, $newUser);
|
||||
|
||||
// Start email confirmation flow if required
|
||||
// Start the email confirmation flow if required
|
||||
if ($this->emailConfirmationService->confirmationRequired() && !$emailConfirmed) {
|
||||
$newUser->save();
|
||||
|
||||
|
||||
@@ -72,6 +72,16 @@ return [
|
||||
// Current host and source for the "DRAWIO" setting will be auto-appended to the sources configured.
|
||||
'iframe_sources' => env('ALLOWED_IFRAME_SOURCES', 'https://*.draw.io https://*.youtube.com https://*.youtube-nocookie.com https://*.vimeo.com'),
|
||||
|
||||
// A list of sources/hostnames that can be loaded as CSS styles within BookStack.
|
||||
// Space separated if multiple. BookStack host domain is auto-inferred.
|
||||
// If not set, a permissive default set is used to reduce potential breakage.
|
||||
'css_sources' => env('ALLOWED_CSS_SOURCES', null),
|
||||
|
||||
// A list of sources/hostnames that can be loaded as image content within BookStack.
|
||||
// Space separated if multiple. BookStack host domain is auto-inferred.
|
||||
// If not set, a permissive default set is used to reduce potential breakage.
|
||||
'image_sources' => env('ALLOWED_IMAGE_SOURCES', null),
|
||||
|
||||
// A list of the sources/hostnames that can be reached by application SSR calls.
|
||||
// This is used wherever users can provide URLs/hosts in-platform, like for webhooks.
|
||||
// Host-specific functionality (usually controlled via other options) like auth
|
||||
|
||||
@@ -30,6 +30,8 @@ class CspService
|
||||
$this->getFrameAncestors(),
|
||||
$this->getFrameSrc(),
|
||||
$this->getScriptSrc(),
|
||||
$this->getStyleSrc(),
|
||||
$this->getImgSrc(),
|
||||
$this->getObjectSrc(),
|
||||
$this->getBaseUri(),
|
||||
];
|
||||
@@ -45,6 +47,8 @@ class CspService
|
||||
$headers = [
|
||||
$this->getFrameSrc(),
|
||||
$this->getScriptSrc(),
|
||||
$this->getStyleSrc(),
|
||||
$this->getImgSrc(),
|
||||
$this->getObjectSrc(),
|
||||
$this->getBaseUri(),
|
||||
];
|
||||
@@ -115,6 +119,22 @@ class CspService
|
||||
return "object-src 'self'";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates CSP 'style-src' rule to restrict where styles can be loaded from.
|
||||
*/
|
||||
protected function getStyleSrc(): string
|
||||
{
|
||||
return 'style-src ' . implode(' ', $this->getAllowedStyleSources());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates CSP 'img-src' rule to restrict where images can be loaded from.
|
||||
*/
|
||||
protected function getImgSrc(): string
|
||||
{
|
||||
return 'img-src ' . implode(' ', $this->getAllowedImageSources());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates CSP 'base-uri' rule to restrict what base tags can be set on
|
||||
* the page to prevent manipulation of relative links.
|
||||
@@ -144,6 +164,51 @@ class CspService
|
||||
return array_filter($sources);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get allowed style sources for the style-src directive.
|
||||
*/
|
||||
protected function getAllowedStyleSources(): array
|
||||
{
|
||||
$configured = config('app.css_sources');
|
||||
|
||||
if (is_string($configured)) {
|
||||
$sources = array_filter(explode(' ', $configured));
|
||||
array_unshift($sources, "'self'");
|
||||
|
||||
return array_values(array_unique($sources));
|
||||
}
|
||||
|
||||
return [
|
||||
"'self'",
|
||||
"'unsafe-inline'",
|
||||
'http:',
|
||||
'https:',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get allowed image sources for the img-src directive.
|
||||
*/
|
||||
protected function getAllowedImageSources(): array
|
||||
{
|
||||
$configured = config('app.image_sources');
|
||||
|
||||
if (is_string($configured)) {
|
||||
$sources = array_filter(explode(' ', $configured));
|
||||
array_unshift($sources, "'self'");
|
||||
|
||||
return array_values(array_unique($sources));
|
||||
}
|
||||
|
||||
return [
|
||||
"'self'",
|
||||
'data:',
|
||||
'blob:',
|
||||
'http:',
|
||||
'https:',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the host name of the configured drawio URL for use in CSP.
|
||||
* Returns empty string if not in use.
|
||||
|
||||
80
composer.lock
generated
80
composer.lock
generated
@@ -62,16 +62,16 @@
|
||||
},
|
||||
{
|
||||
"name": "aws/aws-sdk-php",
|
||||
"version": "3.373.3",
|
||||
"version": "3.373.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||
"reference": "d23edc4cf9cd81cb98b5beb9c1fb3737f535b1e5"
|
||||
"reference": "4402bd10f913e66b7271f44466be8d5ba6c9146e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/d23edc4cf9cd81cb98b5beb9c1fb3737f535b1e5",
|
||||
"reference": "d23edc4cf9cd81cb98b5beb9c1fb3737f535b1e5",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/4402bd10f913e66b7271f44466be8d5ba6c9146e",
|
||||
"reference": "4402bd10f913e66b7271f44466be8d5ba6c9146e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -153,9 +153,9 @@
|
||||
"support": {
|
||||
"forum": "https://github.com/aws/aws-sdk-php/discussions",
|
||||
"issues": "https://github.com/aws/aws-sdk-php/issues",
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.373.3"
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.373.7"
|
||||
},
|
||||
"time": "2026-03-16T18:15:27+00:00"
|
||||
"time": "2026-03-20T18:14:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
@@ -1801,16 +1801,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v12.54.1",
|
||||
"version": "v12.55.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/framework.git",
|
||||
"reference": "325497463e7599cd14224c422c6e5dd2fe832868"
|
||||
"reference": "6d9185a248d101b07eecaf8fd60b18129545fd33"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/325497463e7599cd14224c422c6e5dd2fe832868",
|
||||
"reference": "325497463e7599cd14224c422c6e5dd2fe832868",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/6d9185a248d101b07eecaf8fd60b18129545fd33",
|
||||
"reference": "6d9185a248d101b07eecaf8fd60b18129545fd33",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1926,7 +1926,7 @@
|
||||
"orchestra/testbench-core": "^10.9.0",
|
||||
"pda/pheanstalk": "^5.0.6|^7.0.0",
|
||||
"php-http/discovery": "^1.15",
|
||||
"phpstan/phpstan": "^2.0",
|
||||
"phpstan/phpstan": "^2.1.41",
|
||||
"phpunit/phpunit": "^10.5.35|^11.5.3|^12.0.1",
|
||||
"predis/predis": "^2.3|^3.0",
|
||||
"resend/resend-php": "^0.10.0|^1.0",
|
||||
@@ -2019,20 +2019,20 @@
|
||||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2026-03-10T20:25:56+00:00"
|
||||
"time": "2026-03-18T14:28:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/prompts",
|
||||
"version": "v0.3.14",
|
||||
"version": "v0.3.15",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/prompts.git",
|
||||
"reference": "9f0e371244eedfe2ebeaa72c79c54bb5df6e0176"
|
||||
"reference": "4bb8107ec97651fd3f17f897d6489dbc4d8fb999"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/prompts/zipball/9f0e371244eedfe2ebeaa72c79c54bb5df6e0176",
|
||||
"reference": "9f0e371244eedfe2ebeaa72c79c54bb5df6e0176",
|
||||
"url": "https://api.github.com/repos/laravel/prompts/zipball/4bb8107ec97651fd3f17f897d6489dbc4d8fb999",
|
||||
"reference": "4bb8107ec97651fd3f17f897d6489dbc4d8fb999",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2076,9 +2076,9 @@
|
||||
"description": "Add beautiful and user-friendly forms to your command-line applications.",
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/prompts/issues",
|
||||
"source": "https://github.com/laravel/prompts/tree/v0.3.14"
|
||||
"source": "https://github.com/laravel/prompts/tree/v0.3.15"
|
||||
},
|
||||
"time": "2026-03-01T09:02:38+00:00"
|
||||
"time": "2026-03-17T13:45:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/serializable-closure",
|
||||
@@ -2281,16 +2281,16 @@
|
||||
},
|
||||
{
|
||||
"name": "league/commonmark",
|
||||
"version": "2.8.1",
|
||||
"version": "2.8.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/commonmark.git",
|
||||
"reference": "84b1ca48347efdbe775426f108622a42735a6579"
|
||||
"reference": "59fb075d2101740c337c7216e3f32b36c204218b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/84b1ca48347efdbe775426f108622a42735a6579",
|
||||
"reference": "84b1ca48347efdbe775426f108622a42735a6579",
|
||||
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/59fb075d2101740c337c7216e3f32b36c204218b",
|
||||
"reference": "59fb075d2101740c337c7216e3f32b36c204218b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2384,7 +2384,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-03-05T21:37:03+00:00"
|
||||
"time": "2026-03-19T13:16:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/config",
|
||||
@@ -4027,16 +4027,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpseclib/phpseclib",
|
||||
"version": "3.0.49",
|
||||
"version": "3.0.50",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpseclib/phpseclib.git",
|
||||
"reference": "6233a1e12584754e6b5daa69fe1289b47775c1b9"
|
||||
"reference": "aa6ad8321ed103dc3624fb600a25b66ebf78ec7b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/6233a1e12584754e6b5daa69fe1289b47775c1b9",
|
||||
"reference": "6233a1e12584754e6b5daa69fe1289b47775c1b9",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/aa6ad8321ed103dc3624fb600a25b66ebf78ec7b",
|
||||
"reference": "aa6ad8321ed103dc3624fb600a25b66ebf78ec7b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4117,7 +4117,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpseclib/phpseclib/issues",
|
||||
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.49"
|
||||
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.50"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -4133,7 +4133,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-01-27T09:17:28+00:00"
|
||||
"time": "2026-03-19T02:57:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pragmarx/google2fa",
|
||||
@@ -5154,22 +5154,22 @@
|
||||
},
|
||||
{
|
||||
"name": "socialiteproviders/manager",
|
||||
"version": "v4.8.1",
|
||||
"version": "4.9.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/SocialiteProviders/Manager.git",
|
||||
"reference": "8180ec14bef230ec2351cff993d5d2d7ca470ef4"
|
||||
"reference": "35372dc62787e61e91cfec73f45fd5d5ae0f8891"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/8180ec14bef230ec2351cff993d5d2d7ca470ef4",
|
||||
"reference": "8180ec14bef230ec2351cff993d5d2d7ca470ef4",
|
||||
"url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/35372dc62787e61e91cfec73f45fd5d5ae0f8891",
|
||||
"reference": "35372dc62787e61e91cfec73f45fd5d5ae0f8891",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0",
|
||||
"illuminate/support": "^11.0 || ^12.0 || ^13.0",
|
||||
"laravel/socialite": "^5.5",
|
||||
"php": "^8.1"
|
||||
"php": "^8.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.2",
|
||||
@@ -5224,7 +5224,7 @@
|
||||
"issues": "https://github.com/socialiteproviders/manager/issues",
|
||||
"source": "https://github.com/socialiteproviders/manager"
|
||||
},
|
||||
"time": "2025-02-24T19:33:30+00:00"
|
||||
"time": "2026-03-18T22:13:24+00:00"
|
||||
},
|
||||
{
|
||||
"name": "socialiteproviders/microsoft-azure",
|
||||
@@ -9169,11 +9169,11 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "2.1.41",
|
||||
"version": "2.1.42",
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/a2eae8f20856b3afe74bf1f9726ce8c11438e300",
|
||||
"reference": "a2eae8f20856b3afe74bf1f9726ce8c11438e300",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/1279e1ce86ba768f0780c9d889852b4e02ff40d0",
|
||||
"reference": "1279e1ce86ba768f0780c9d889852b4e02ff40d0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -9218,7 +9218,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-03-16T18:24:10+00:00"
|
||||
"time": "2026-03-17T14:58:32+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
|
||||
@@ -31,13 +31,55 @@ BookStack has a large suite of PHP tests to cover application functionality. We
|
||||
|
||||
For details about setting-up, running and writing tests please see the [php-testing.md document](php-testing.md).
|
||||
|
||||
## Content Security Policy Controls
|
||||
|
||||
BookStack enforces a Content Security Policy (CSP) response header to reduce risk from injected content and untrusted embeds.
|
||||
|
||||
For backward compatibility, image and CSS controls are intentionally permissive by default, but can be tightened via environment options.
|
||||
|
||||
### Related Environment Options
|
||||
|
||||
These values are defined in `.env.example.complete`:
|
||||
|
||||
- `ALLOWED_CSS_SOURCES`
|
||||
- Controls allowed `style-src` sources.
|
||||
- Defaults to a permissive fallback if unset.
|
||||
- `ALLOWED_IMAGE_SOURCES`
|
||||
- Controls allowed `img-src` sources.
|
||||
- Defaults to a permissive fallback if unset.
|
||||
|
||||
Values should be space-separated source expressions.
|
||||
|
||||
### Example Configurations
|
||||
|
||||
Allow Google Fonts CSS and local styles only:
|
||||
|
||||
```bash
|
||||
ALLOWED_CSS_SOURCES="https://fonts.googleapis.com"
|
||||
```
|
||||
|
||||
Allow local images, embedded data images, and a dedicated image CDN:
|
||||
|
||||
```bash
|
||||
ALLOWED_IMAGE_SOURCES="data: https://images.example.com"
|
||||
```
|
||||
|
||||
### Tightening Guidance
|
||||
|
||||
When hardening a deployment:
|
||||
|
||||
1. Start with defaults to avoid unexpected breakage.
|
||||
2. Set explicit `ALLOWED_CSS_SOURCES` and `ALLOWED_IMAGE_SOURCES` values for the domains you actually use.
|
||||
3. Test key workflows (editor, page display, theme assets, external embeds) and browser console CSP warnings.
|
||||
4. Remove unnecessary protocols and hosts over time.
|
||||
|
||||
## Code Standards
|
||||
|
||||
We use tools to manage code standards and formatting within the project. If submitting a PR, formatting as per our project standards would help for clarity but don't worry too much about using/understanding these tools as we can always address issues at a later stage when they're picked up by our automated tools.
|
||||
|
||||
### PHP
|
||||
|
||||
PHP code standards are managed by [using PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer).
|
||||
PHP code standards are managed by [using PHP_CodeSniffer](https://github.com/PHPCSStandards/PHP_CodeSniffer).
|
||||
Static analysis is in place using [PHPStan](https://phpstan.org/) & [Larastan](https://github.com/nunomaduro/larastan).
|
||||
The below commands can be used to utilise these tools:
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ return [
|
||||
'sort_rule_op_chapters_first' => 'Kapitoly jako první',
|
||||
'sort_rule_op_chapters_last' => 'Kapitoly jako poslední',
|
||||
'sorting_page_limits' => 'Počet zobrazených položek na stránce',
|
||||
'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
|
||||
'sorting_page_limits_desc' => 'Nastavte, kolik položek se má zobrazit na stránce v různých seznamech na webu. Obvykle bude nižší počet výkonnější, zatímco vyšší počet eliminuje nutnost proklikávat se několika stránkami. Doporučuje se použít násobek čísla 6.',
|
||||
|
||||
// Maintenance settings
|
||||
'maint' => 'Údržba',
|
||||
|
||||
@@ -109,7 +109,7 @@ return [
|
||||
'import_zip_cant_read' => 'ZIP-Datei konnte nicht gelesen werden.',
|
||||
'import_zip_cant_decode_data' => 'ZIP data.json konnte nicht gefunden und dekodiert werden.',
|
||||
'import_zip_no_data' => 'ZIP-Datei Daten haben kein erwartetes Buch, Kapitel oder Seiteninhalt.',
|
||||
'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'import_zip_data_too_large' => 'Der Inhalt der ZIP data.json überschreitet die maximale Dateigröße der Anwendung.',
|
||||
'import_validation_failed' => 'ZIP Import konnte mit Fehlern nicht validiert werden:',
|
||||
'import_zip_failed_notification' => 'Importieren der ZIP-Datei fehlgeschlagen.',
|
||||
'import_perms_books' => 'Ihnen fehlt die erforderliche Berechtigung, um Bücher zu erstellen.',
|
||||
@@ -125,7 +125,7 @@ return [
|
||||
'api_incorrect_token_secret' => 'Das Kennwort für das angegebene API-Token ist falsch',
|
||||
'api_user_no_api_permission' => 'Der Besitzer des verwendeten API-Tokens hat keine Berechtigung für API-Aufrufe',
|
||||
'api_user_token_expired' => 'Das verwendete Autorisierungstoken ist abgelaufen',
|
||||
'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication',
|
||||
'api_cookie_auth_only_get' => 'Nur GET Anfragen sind erlaubt, wenn die API mit Cookie-basierter Authentifizierung verwendet wird',
|
||||
|
||||
// Settings & Maintenance
|
||||
'maintenance_test_email_failure' => 'Fehler beim Versenden einer Test E-Mail:',
|
||||
|
||||
@@ -11,8 +11,8 @@ return [
|
||||
'updated_page_subject' => 'Aktualisierte Seite: :pageName',
|
||||
'updated_page_intro' => 'Eine Seite wurde in :appName aktualisiert:',
|
||||
'updated_page_debounce' => 'Um eine Flut von Benachrichtigungen zu vermeiden, werden Sie für eine gewisse Zeit keine Benachrichtigungen für weitere Bearbeitungen dieser Seite durch denselben Bearbeiter erhalten.',
|
||||
'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName',
|
||||
'comment_mention_intro' => 'You were mentioned in a comment on :appName:',
|
||||
'comment_mention_subject' => 'Sie wurden in einem Kommentar auf der Seite :pageName erwähnt',
|
||||
'comment_mention_intro' => 'Sie wurden in einem Kommentar zu :appName: erwähnt',
|
||||
|
||||
'detail_page_name' => 'Name der Seite:',
|
||||
'detail_page_path' => 'Seitenpfad:',
|
||||
|
||||
@@ -23,7 +23,7 @@ return [
|
||||
'notifications_desc' => 'Legen Sie fest, welche E-Mail-Benachrichtigungen Sie erhalten, wenn bestimmte Aktivitäten im System durchgeführt werden.',
|
||||
'notifications_opt_own_page_changes' => 'Benachrichtigung bei Änderungen an eigenen Seiten',
|
||||
'notifications_opt_own_page_comments' => 'Benachrichtigung bei Kommentaren an eigenen Seiten',
|
||||
'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment',
|
||||
'notifications_opt_comment_mentions' => 'Bei Erwähnung mich benachrichtigen',
|
||||
'notifications_opt_comment_replies' => 'Bei Antworten auf meine Kommentare benachrichtigen',
|
||||
'notifications_save' => 'Einstellungen speichern',
|
||||
'notifications_update_success' => 'Benachrichtigungseinstellungen wurden aktualisiert!',
|
||||
|
||||
@@ -109,7 +109,7 @@ return [
|
||||
'import_zip_cant_read' => 'ZIP-Datei konnte nicht gelesen werden.',
|
||||
'import_zip_cant_decode_data' => 'Konnte Inhalt der data.json im ZIP nicht finden und dekodieren.',
|
||||
'import_zip_no_data' => 'ZIP-Datei hat kein erwartetes Buch, Kapitel oder Seiteninhalt.',
|
||||
'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'import_zip_data_too_large' => 'Der Inhalt der ZIP data.json überschreitet die maximale Dateigröße der Anwendung.',
|
||||
'import_validation_failed' => 'ZIP Import konnte aufgrund folgender Fehler nicht validiert werden:',
|
||||
'import_zip_failed_notification' => 'Importieren der ZIP-Datei fehlgeschlagen.',
|
||||
'import_perms_books' => 'Dir fehlt die erforderliche Berechtigung, um Bücher zu erstellen.',
|
||||
@@ -125,7 +125,7 @@ return [
|
||||
'api_incorrect_token_secret' => 'Das für den API-Token angegebene geheime Token ist falsch',
|
||||
'api_user_no_api_permission' => 'Der Besitzer des verwendeten API-Token hat keine Berechtigung für API-Aufrufe',
|
||||
'api_user_token_expired' => 'Das verwendete Autorisierungs-Token ist abgelaufen',
|
||||
'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication',
|
||||
'api_cookie_auth_only_get' => 'Nur GET Anfragen sind erlaubt, wenn die API mit Cookie-basierter Authentifizierung verwendet wird',
|
||||
|
||||
// Settings & Maintenance
|
||||
'maintenance_test_email_failure' => 'Fehler beim Senden einer Test E-Mail:',
|
||||
|
||||
@@ -11,8 +11,8 @@ return [
|
||||
'updated_page_subject' => 'Aktualisierte Seite: :pageName',
|
||||
'updated_page_intro' => 'Eine Seite wurde in :appName aktualisiert:',
|
||||
'updated_page_debounce' => 'Um eine Flut von Benachrichtigungen zu vermeiden, wirst du für eine gewisse Zeit keine Benachrichtigungen für weitere Bearbeitungen dieser Seite durch denselben Bearbeiter erhalten.',
|
||||
'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName',
|
||||
'comment_mention_intro' => 'You were mentioned in a comment on :appName:',
|
||||
'comment_mention_subject' => 'Sie wurden in einem Kommentar auf der Seite :pageName erwähnt',
|
||||
'comment_mention_intro' => 'Sie wurden in einem Kommentar zu :appName: erwähnt',
|
||||
|
||||
'detail_page_name' => 'Seitenname:',
|
||||
'detail_page_path' => 'Seitenpfad:',
|
||||
|
||||
@@ -23,7 +23,7 @@ return [
|
||||
'notifications_desc' => 'Lege fest, welche E-Mail-Benachrichtigungen du erhältst, wenn bestimmte Aktivitäten im System durchgeführt werden.',
|
||||
'notifications_opt_own_page_changes' => 'Benachrichtigung bei Änderungen an eigenen Seiten',
|
||||
'notifications_opt_own_page_comments' => 'Benachrichtigung bei Kommentaren an eigenen Seiten',
|
||||
'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment',
|
||||
'notifications_opt_comment_mentions' => 'Bei Erwähnung mich benachrichtigen',
|
||||
'notifications_opt_comment_replies' => 'Bei Antworten auf meine Kommentare benachrichtigen',
|
||||
'notifications_save' => 'Einstellungen speichern',
|
||||
'notifications_update_success' => 'Benachrichtigungseinstellungen wurden aktualisiert!',
|
||||
|
||||
@@ -104,7 +104,7 @@ return [
|
||||
'sort_rule_op_chapters_first' => 'Chapitres en premier',
|
||||
'sort_rule_op_chapters_last' => 'Chapitres en dernier',
|
||||
'sorting_page_limits' => 'Limite d\'affichage par page',
|
||||
'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
|
||||
'sorting_page_limits_desc' => 'Définissez le nombre d’éléments à afficher par page dans les différentes listes du système. En général, un nombre plus faible offre de meilleures performances, tandis qu’un nombre plus élevé réduit le besoin de naviguer entre plusieurs pages. Il est recommandé d’utiliser un multiple de 6.',
|
||||
|
||||
// Maintenance settings
|
||||
'maint' => 'Maintenance',
|
||||
|
||||
@@ -104,7 +104,7 @@ return [
|
||||
'sort_rule_op_chapters_first' => 'チャプタを最初に',
|
||||
'sort_rule_op_chapters_last' => 'チャプタを最後に',
|
||||
'sorting_page_limits' => 'ページング表示制限',
|
||||
'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
|
||||
'sorting_page_limits_desc' => 'システム内の各種リストで1ページに表示するアイテム数を設定します。 通常、少ない数に設定するとパフォーマンスが向上し、多い数に設定するとページの移動操作が少なくなります。6 の倍数に設定することをお勧めします。',
|
||||
|
||||
// Maintenance settings
|
||||
'maint' => 'メンテナンス',
|
||||
|
||||
@@ -102,6 +102,7 @@ Big thanks to these companies for supporting the project.
|
||||
## 🛠️ Development & Testing
|
||||
|
||||
Please see our [development docs](dev/docs/development.md) for full details regarding work on the BookStack source code.
|
||||
For details on Content Security Policy controls (including image and CSS source options), see the **Content Security Policy Controls** section in the [development docs](dev/docs/development.md).
|
||||
|
||||
If you're just looking to customize or extend your own BookStack instance, take a look at our [Hacking BookStack documentation page](https://www.bookstackapp.com/docs/admin/hacking-bookstack/) for details on various options to achieve this without altering the BookStack source code.
|
||||
|
||||
@@ -178,7 +179,7 @@ Note: This is not an exhaustive list of all libraries and projects that would be
|
||||
* [phpseclib](https://github.com/phpseclib/phpseclib) - _[MIT](https://github.com/phpseclib/phpseclib/blob/master/LICENSE)_
|
||||
* [Clockwork](https://github.com/itsgoingd/clockwork) - _[MIT](https://github.com/itsgoingd/clockwork/blob/master/LICENSE)_
|
||||
* [PHPStan](https://phpstan.org/) & [Larastan](https://github.com/nunomaduro/larastan) - _[MIT](https://github.com/phpstan/phpstan/blob/master/LICENSE) and [MIT](https://github.com/nunomaduro/larastan/blob/master/LICENSE.md)_
|
||||
* [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) - _[BSD 3-Clause](https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt)_
|
||||
* [PHP_CodeSniffer](https://github.com/PHPCSStandards/PHP_CodeSniffer) - _[BSD 3-Clause](https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt)_
|
||||
* [JakeArchibald/IDB-Keyval](https://github.com/jakearchibald/idb-keyval) - _[Apache-2.0](https://github.com/jakearchibald/idb-keyval/blob/main/LICENCE)_
|
||||
* [HTML Purifier](https://github.com/ezyang/htmlpurifier) and [htmlpurifier-html5](https://github.com/xemlock/htmlpurifier-html5) - _[LGPL-2.1](https://github.com/ezyang/htmlpurifier/blob/master/LICENSE) and [MIT](https://github.com/xemlock/htmlpurifier-html5/blob/master/LICENSE)_
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ function setSummary(editor, summaryContent) {
|
||||
}
|
||||
summary.textContent = summaryContent;
|
||||
});
|
||||
|
||||
editor.selection.select(details);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,8 +204,12 @@ function register(editor) {
|
||||
});
|
||||
|
||||
editor.on('dblclick', event => {
|
||||
if (!getSelectedDetailsBlock(editor) || event.target.closest('doc-root')) return;
|
||||
showDetailLabelEditWindow(editor);
|
||||
const domElClass = event?.target?.ownerDocument?.defaultView?.HTMLDetailsElement;
|
||||
if (domElClass && event.target instanceof domElClass && getSelectedDetailsBlock(editor)) {
|
||||
showDetailLabelEditWindow(editor);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
editor.ui.registry.addButton('toggledetails', {
|
||||
|
||||
@@ -45,6 +45,7 @@ import {LineBreakNode} from './nodes/LexicalLineBreakNode';
|
||||
import {ParagraphNode} from './nodes/LexicalParagraphNode';
|
||||
import {RootNode} from './nodes/LexicalRootNode';
|
||||
import {TabNode} from './nodes/LexicalTabNode';
|
||||
import {EditorUiContext} from "../../ui/framework/core";
|
||||
|
||||
export type Spread<T1, T2> = Omit<T2, keyof T1> & T1;
|
||||
|
||||
@@ -621,6 +622,8 @@ export class LexicalEditor {
|
||||
_editable: boolean;
|
||||
/** @internal */
|
||||
_blockCursorElement: null | HTMLDivElement;
|
||||
/** @internal */
|
||||
_context: null | EditorUiContext;
|
||||
|
||||
/** @internal */
|
||||
constructor(
|
||||
@@ -682,6 +685,7 @@ export class LexicalEditor {
|
||||
this._headless = parentEditor !== null && parentEditor._headless;
|
||||
this._window = null;
|
||||
this._blockCursorElement = null;
|
||||
this._context = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1285,6 +1289,21 @@ export class LexicalEditor {
|
||||
triggerListeners('editable', this, true, editable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the UI context that this editor is intended to be part of.
|
||||
*/
|
||||
setUiContext(context: EditorUiContext) {
|
||||
this._context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the UI context that this editor is considered to be part of.
|
||||
*/
|
||||
getUiContext(): EditorUiContext|null {
|
||||
return this._context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON-serializable javascript object NOT a JSON string.
|
||||
* You still must call JSON.stringify (or something else) to turn the
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from 'lexical';
|
||||
|
||||
import {extractDirectionFromElement} from "lexical/nodes/common";
|
||||
import {$showDetailsForm} from "../../ui/defaults/forms/objects";
|
||||
|
||||
export type SerializedDetailsNode = Spread<{
|
||||
id: string;
|
||||
@@ -90,6 +91,16 @@ export class DetailsNode extends ElementNode {
|
||||
});
|
||||
});
|
||||
|
||||
summary.addEventListener('dblclick', event => {
|
||||
event.preventDefault();
|
||||
const uiContext = _editor.getUiContext();
|
||||
if (uiContext) {
|
||||
_editor.read(() => {
|
||||
$showDetailsForm(this, uiContext);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
el.append(summary);
|
||||
|
||||
return el;
|
||||
|
||||
@@ -221,7 +221,7 @@ export const detailsEditLabel: EditorButtonDefinition = {
|
||||
if ($isDetailsNode(details)) {
|
||||
$showDetailsForm(details, context);
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
isActive(selection: BaseSelection | null): boolean {
|
||||
return false;
|
||||
|
||||
@@ -29,7 +29,7 @@ export class EditorUIManager {
|
||||
setContext(context: EditorUiContext) {
|
||||
this.context = context;
|
||||
this.setupEventListeners();
|
||||
this.setupEditor(context.editor);
|
||||
this.setupEditor(context.editor, context);
|
||||
}
|
||||
|
||||
getContext(): EditorUiContext {
|
||||
@@ -256,7 +256,10 @@ export class EditorUIManager {
|
||||
}
|
||||
}
|
||||
|
||||
protected setupEditor(editor: LexicalEditor) {
|
||||
protected setupEditor(editor: LexicalEditor, context: EditorUiContext) {
|
||||
// Pass the context to the editor
|
||||
editor.setUiContext(context);
|
||||
|
||||
// Register our DOM decorate listener with the editor
|
||||
const domDecorateListener: DecoratorListener<EditorDecoratorAdapter> = (decorators: Record<NodeKey, EditorDecoratorAdapter>) => {
|
||||
editor.getEditorState().read(() => {
|
||||
|
||||
@@ -188,6 +188,30 @@ class RegistrationTest extends TestCase
|
||||
$resp->assertSee('The password must be at least 8 characters.');
|
||||
}
|
||||
|
||||
public function test_registration_input_filtered_to_validated_input()
|
||||
{
|
||||
$this->setSettings(['registration-enabled' => 'true']);
|
||||
$roleIds = Role::all()->pluck('id')->toArray();
|
||||
|
||||
$resp = $this->post('/register', [
|
||||
'name' => 'Barry',
|
||||
'email' => 'barry@example.com',
|
||||
'password' => 'superpassword',
|
||||
'password_confirmation' => 'superpassword',
|
||||
'external_auth_id' => 'ext5691284',
|
||||
'roles' => $roleIds,
|
||||
]);
|
||||
|
||||
$resp->assertRedirect('/');
|
||||
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$this->assertNotNull($user);
|
||||
$this->assertFalse($user->isGuest());
|
||||
$this->assertEmpty($user->external_auth_id);
|
||||
$this->assertEquals(0, $user->roles()->count());
|
||||
}
|
||||
|
||||
public function test_registration_simple_honeypot_active()
|
||||
{
|
||||
$this->setSettings(['registration-enabled' => 'true']);
|
||||
|
||||
@@ -151,6 +151,42 @@ class SecurityHeaderTest extends TestCase
|
||||
$this->assertEquals('frame-src \'self\' https://example.com https://diagrams.example.com:8080', $scriptHeader);
|
||||
}
|
||||
|
||||
public function test_style_src_csp_header_set_to_permissive_defaults_when_not_configured()
|
||||
{
|
||||
$resp = $this->get('/');
|
||||
$header = $this->getCspHeader($resp, 'style-src');
|
||||
|
||||
$this->assertEquals("style-src 'self' 'unsafe-inline' http: https:", $header);
|
||||
}
|
||||
|
||||
public function test_style_src_csp_header_can_be_overridden_by_config()
|
||||
{
|
||||
config()->set('app.css_sources', 'https://fonts.example.com');
|
||||
|
||||
$resp = $this->get('/');
|
||||
$header = $this->getCspHeader($resp, 'style-src');
|
||||
|
||||
$this->assertEquals("style-src 'self' https://fonts.example.com", $header);
|
||||
}
|
||||
|
||||
public function test_img_src_csp_header_set_to_permissive_defaults_when_not_configured()
|
||||
{
|
||||
$resp = $this->get('/');
|
||||
$header = $this->getCspHeader($resp, 'img-src');
|
||||
|
||||
$this->assertEquals("img-src 'self' data: blob: http: https:", $header);
|
||||
}
|
||||
|
||||
public function test_img_src_csp_header_can_be_overridden_by_config()
|
||||
{
|
||||
config()->set('app.image_sources', 'https://images.example.com data:');
|
||||
|
||||
$resp = $this->get('/');
|
||||
$header = $this->getCspHeader($resp, 'img-src');
|
||||
|
||||
$this->assertEquals("img-src 'self' https://images.example.com data:", $header);
|
||||
}
|
||||
|
||||
public function test_cache_control_headers_are_set_on_responses()
|
||||
{
|
||||
// Public access
|
||||
|
||||
Reference in New Issue
Block a user