mirror of
https://github.com/BookStackApp/BookStack.git
synced 2026-05-04 18:08:46 +03:00
Compare commits
4 Commits
v26.03.4
...
GamerClass
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25c5170da2 | ||
|
|
08481b0372 | ||
|
|
b76e632f08 | ||
|
|
9589492081 |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -8,10 +8,10 @@ Homestead.yaml
|
||||
.idea
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
/public/dist/*.map
|
||||
/public/dist
|
||||
/public/plugins
|
||||
/public/css/*.map
|
||||
/public/js/*.map
|
||||
/public/css
|
||||
/public/js
|
||||
/public/bower
|
||||
/public/build/
|
||||
/public/favicon.ico
|
||||
|
||||
@@ -33,6 +33,7 @@ class Kernel extends HttpKernel
|
||||
\BookStack\Http\Middleware\StartSessionExtended::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\BookStack\Http\Middleware\VerifyCsrfToken::class,
|
||||
\BookStack\Http\Middleware\Impersonate::class,
|
||||
\BookStack\Http\Middleware\CheckEmailConfirmed::class,
|
||||
\BookStack\Http\Middleware\RunThemeActions::class,
|
||||
\BookStack\Http\Middleware\Localization::class,
|
||||
|
||||
32
app/Http/Middleware/Impersonate.php
Normal file
32
app/Http/Middleware/Impersonate.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Http\Middleware;
|
||||
|
||||
use BookStack\Permissions\Permission;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class Impersonate
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Closure(Request): (Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$impersonateId = session('impersonate', null);
|
||||
if (empty($impersonateId)) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
$realUser = auth()->user();
|
||||
if ($realUser && $realUser->can(Permission::UsersManage)) {
|
||||
Auth::onceUsingId($impersonateId);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -120,14 +120,8 @@ class SearchRunner
|
||||
$filter = function (EloquentBuilder $query) use ($exact) {
|
||||
$inputTerm = str_replace('\\', '\\\\', $exact->value);
|
||||
$query->where('name', 'like', '%' . $inputTerm . '%')
|
||||
->orWhere(function (EloquentBuilder $query) use ($inputTerm) {
|
||||
$query->whereNotNull('description')
|
||||
->where('description', 'like', '%' . $inputTerm . '%');
|
||||
})
|
||||
->orWhere(function (EloquentBuilder $query) use ($inputTerm) {
|
||||
$query->whereNotNull('text')
|
||||
->where('text', 'like', '%' . $inputTerm . '%');
|
||||
});
|
||||
->orWhere('description', 'like', '%' . $inputTerm . '%')
|
||||
->orWhere('text', 'like', '%' . $inputTerm . '%');
|
||||
};
|
||||
|
||||
$exact->negated ? $entityQuery->whereNot($filter) : $entityQuery->where($filter);
|
||||
|
||||
@@ -195,7 +195,6 @@ class AttachmentController extends Controller
|
||||
$this->validate($request, [
|
||||
'order' => ['required', 'array'],
|
||||
]);
|
||||
|
||||
$page = $this->pageQueries->findVisibleByIdOrFail($pageId);
|
||||
$this->checkOwnablePermission(Permission::PageUpdate, $page);
|
||||
|
||||
@@ -222,6 +221,8 @@ class AttachmentController extends Controller
|
||||
throw new NotFoundException(trans('errors.attachment_not_found'));
|
||||
}
|
||||
|
||||
$this->checkOwnablePermission(Permission::PageView, $page);
|
||||
|
||||
if ($attachment->external) {
|
||||
return redirect($attachment->path);
|
||||
}
|
||||
@@ -246,13 +247,6 @@ class AttachmentController extends Controller
|
||||
{
|
||||
/** @var Attachment $attachment */
|
||||
$attachment = Attachment::query()->findOrFail($attachmentId);
|
||||
|
||||
try {
|
||||
$this->pageQueries->findVisibleByIdOrFail($attachment->uploaded_to);
|
||||
} catch (NotFoundException $exception) {
|
||||
throw new NotFoundException(trans('errors.attachment_not_found'));
|
||||
}
|
||||
|
||||
$this->checkOwnablePermission(Permission::AttachmentDelete, $attachment);
|
||||
$this->attachmentService->deleteFile($attachment);
|
||||
|
||||
|
||||
@@ -191,6 +191,36 @@ class UserController extends Controller
|
||||
return view('users.delete', ['user' => $user]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start impersonating the specified user.
|
||||
*/
|
||||
public function impersonate(int $id)
|
||||
{
|
||||
$this->checkPermission(Permission::UsersManage);
|
||||
|
||||
$user = $this->userRepo->getById($id);
|
||||
|
||||
if ($user->isGuest() || $user->id === user()->id) {
|
||||
$this->showErrorNotification(trans('errors.users_cannot_impersonate'));
|
||||
return redirect("/settings/users/{$id}");
|
||||
}
|
||||
|
||||
session(['impersonate' => $user->id]);
|
||||
|
||||
return redirect('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop impersonating and return to user edit page.
|
||||
*/
|
||||
public function stopImpersonate()
|
||||
{
|
||||
$userId = session('impersonate');
|
||||
session()->forget('impersonate');
|
||||
|
||||
return redirect("/settings/users/{$userId}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified user from storage.
|
||||
*
|
||||
|
||||
@@ -8,10 +8,6 @@ use BookStack\Exceptions\HttpFetchException;
|
||||
* Validate the host we're connecting to when making a server-side-request.
|
||||
* Will use the given hosts config if given during construction otherwise
|
||||
* will look to the app configured config.
|
||||
*
|
||||
* The config format is a space-seperated list of URL prefixes which should contain the
|
||||
* protocol and host. It can optionally define a path prefix as part of the URL.
|
||||
* Wildcards, via a '*', can be used within these elements to match anything but a '/'.
|
||||
*/
|
||||
class SsrUrlValidator
|
||||
{
|
||||
@@ -52,34 +48,15 @@ class SsrUrlValidator
|
||||
{
|
||||
$pattern = rtrim(trim($pattern), '/');
|
||||
$url = trim($url);
|
||||
$urlParts = parse_url($url);
|
||||
|
||||
if (empty($pattern) || empty($url) || $urlParts === false) {
|
||||
if (empty($pattern) || empty($url)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent potential tricks using percent encoded slashes
|
||||
if (str_contains(strtolower($urlParts['host'] ?? ''), '%2f')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disregard query and fragment
|
||||
$url = explode('?', $url, 2)[0];
|
||||
$url = explode('#', $url, 2)[0];
|
||||
|
||||
// Disregard userinfo if existing
|
||||
if (!empty($urlParts['user']) || !empty($urlParts['pass'])) {
|
||||
[$start, $postUserinfo] = explode('@', $url, 2);
|
||||
$preUserinfo = explode('//', $start, 2)[0];
|
||||
$url = ($preUserinfo ? $preUserinfo . '//' : '') . $postUserinfo;
|
||||
}
|
||||
|
||||
// Prepare pattern
|
||||
$quoted = preg_quote($pattern, '/');
|
||||
$regexPattern = str_replace('\*', '[^\/]*', $quoted);
|
||||
$regexPattern = str_replace('\*', '.*', $quoted);
|
||||
|
||||
// Check against our URL
|
||||
return preg_match('/^' . $regexPattern . '($|\/.*$)/i', $url);
|
||||
return preg_match('/^' . $regexPattern . '($|\/.*$|#.*$)/i', $url);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
254
composer.lock
generated
254
composer.lock
generated
@@ -62,16 +62,16 @@
|
||||
},
|
||||
{
|
||||
"name": "aws/aws-sdk-php",
|
||||
"version": "3.379.8",
|
||||
"version": "3.376.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||
"reference": "856ddf3d241c29132fe1eb946e112351ab043542"
|
||||
"reference": "2081f8db174df4bb8842aed3b7b513590ee9d219"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/856ddf3d241c29132fe1eb946e112351ab043542",
|
||||
"reference": "856ddf3d241c29132fe1eb946e112351ab043542",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2081f8db174df4bb8842aed3b7b513590ee9d219",
|
||||
"reference": "2081f8db174df4bb8842aed3b7b513590ee9d219",
|
||||
"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.379.8"
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.376.3"
|
||||
},
|
||||
"time": "2026-04-27T19:13:21+00:00"
|
||||
"time": "2026-04-03T18:07:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
@@ -985,12 +985,12 @@
|
||||
"version": "v7.0.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/googleapis/php-jwt.git",
|
||||
"url": "https://github.com/firebase/php-jwt.git",
|
||||
"reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/googleapis/php-jwt/zipball/47ad26bab5e7c70ae8a6f08ed25ff83631121380",
|
||||
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/47ad26bab5e7c70ae8a6f08ed25ff83631121380",
|
||||
"reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380",
|
||||
"shasum": ""
|
||||
},
|
||||
@@ -1039,8 +1039,8 @@
|
||||
"php"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/googleapis/php-jwt/issues",
|
||||
"source": "https://github.com/googleapis/php-jwt/tree/v7.0.5"
|
||||
"issues": "https://github.com/firebase/php-jwt/issues",
|
||||
"source": "https://github.com/firebase/php-jwt/tree/v7.0.5"
|
||||
},
|
||||
"time": "2026-04-01T20:38:03+00:00"
|
||||
},
|
||||
@@ -1802,16 +1802,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v12.58.0",
|
||||
"version": "v12.56.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/framework.git",
|
||||
"reference": "6172ae1f44ba5d89e111057ee4a4e7c27f5a610d"
|
||||
"reference": "dac16d424b59debb2273910dde88eb7050a2a709"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/6172ae1f44ba5d89e111057ee4a4e7c27f5a610d",
|
||||
"reference": "6172ae1f44ba5d89e111057ee4a4e7c27f5a610d",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/dac16d424b59debb2273910dde88eb7050a2a709",
|
||||
"reference": "dac16d424b59debb2273910dde88eb7050a2a709",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1852,8 +1852,8 @@
|
||||
"symfony/mailer": "^7.2.0",
|
||||
"symfony/mime": "^7.2.0",
|
||||
"symfony/polyfill-php83": "^1.33",
|
||||
"symfony/polyfill-php84": "^1.34",
|
||||
"symfony/polyfill-php85": "^1.34",
|
||||
"symfony/polyfill-php84": "^1.33",
|
||||
"symfony/polyfill-php85": "^1.33",
|
||||
"symfony/process": "^7.2.0",
|
||||
"symfony/routing": "^7.2.0",
|
||||
"symfony/uid": "^7.2.0",
|
||||
@@ -2020,20 +2020,20 @@
|
||||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2026-04-26T16:42:04+00:00"
|
||||
"time": "2026-03-26T14:51:54+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/prompts",
|
||||
"version": "v0.3.17",
|
||||
"version": "v0.3.16",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/prompts.git",
|
||||
"reference": "6a82ac19a28b916ae0885828795dbd4c59d9a818"
|
||||
"reference": "11e7d5f93803a2190b00e145142cb00a33d17ad2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/prompts/zipball/6a82ac19a28b916ae0885828795dbd4c59d9a818",
|
||||
"reference": "6a82ac19a28b916ae0885828795dbd4c59d9a818",
|
||||
"url": "https://api.github.com/repos/laravel/prompts/zipball/11e7d5f93803a2190b00e145142cb00a33d17ad2",
|
||||
"reference": "11e7d5f93803a2190b00e145142cb00a33d17ad2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2077,22 +2077,22 @@
|
||||
"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.17"
|
||||
"source": "https://github.com/laravel/prompts/tree/v0.3.16"
|
||||
},
|
||||
"time": "2026-04-20T16:07:33+00:00"
|
||||
"time": "2026-03-23T14:35:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/serializable-closure",
|
||||
"version": "v2.0.13",
|
||||
"version": "v2.0.10",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/serializable-closure.git",
|
||||
"reference": "b566ee0dd251f3c4078bed003a7ce015f5ea6dce"
|
||||
"reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/serializable-closure/zipball/b566ee0dd251f3c4078bed003a7ce015f5ea6dce",
|
||||
"reference": "b566ee0dd251f3c4078bed003a7ce015f5ea6dce",
|
||||
"url": "https://api.github.com/repos/laravel/serializable-closure/zipball/870fc81d2f879903dfc5b60bf8a0f94a1609e669",
|
||||
"reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2140,20 +2140,20 @@
|
||||
"issues": "https://github.com/laravel/serializable-closure/issues",
|
||||
"source": "https://github.com/laravel/serializable-closure"
|
||||
},
|
||||
"time": "2026-04-16T14:03:50+00:00"
|
||||
"time": "2026-02-20T19:59:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/socialite",
|
||||
"version": "v5.27.0",
|
||||
"version": "v5.26.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/socialite.git",
|
||||
"reference": "40e0757a75637c7b2dff05d3286b0d8fc25e5c0e"
|
||||
"reference": "db6ec2ee967b7f06412c3a0cf1daaf072f4752a4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/socialite/zipball/40e0757a75637c7b2dff05d3286b0d8fc25e5c0e",
|
||||
"reference": "40e0757a75637c7b2dff05d3286b0d8fc25e5c0e",
|
||||
"url": "https://api.github.com/repos/laravel/socialite/zipball/db6ec2ee967b7f06412c3a0cf1daaf072f4752a4",
|
||||
"reference": "db6ec2ee967b7f06412c3a0cf1daaf072f4752a4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2212,7 +2212,7 @@
|
||||
"issues": "https://github.com/laravel/socialite/issues",
|
||||
"source": "https://github.com/laravel/socialite"
|
||||
},
|
||||
"time": "2026-04-24T14:05:47+00:00"
|
||||
"time": "2026-03-29T14:50:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/tinker",
|
||||
@@ -3362,16 +3362,16 @@
|
||||
},
|
||||
{
|
||||
"name": "nesbot/carbon",
|
||||
"version": "3.11.4",
|
||||
"version": "3.11.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/CarbonPHP/carbon.git",
|
||||
"reference": "e890471a3494740f7d9326d72ce6a8c559ffee60"
|
||||
"reference": "6a7e652845bb018c668220c2a545aded8594fbbf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/e890471a3494740f7d9326d72ce6a8c559ffee60",
|
||||
"reference": "e890471a3494740f7d9326d72ce6a8c559ffee60",
|
||||
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/6a7e652845bb018c668220c2a545aded8594fbbf",
|
||||
"reference": "6a7e652845bb018c668220c2a545aded8594fbbf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3463,7 +3463,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-04-07T09:57:54+00:00"
|
||||
"time": "2026-03-11T17:23:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nette/schema",
|
||||
@@ -4028,16 +4028,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpseclib/phpseclib",
|
||||
"version": "3.0.52",
|
||||
"version": "3.0.50",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpseclib/phpseclib.git",
|
||||
"reference": "2adaefc83df2ec548558307690f376dd7d4f4fce"
|
||||
"reference": "aa6ad8321ed103dc3624fb600a25b66ebf78ec7b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/2adaefc83df2ec548558307690f376dd7d4f4fce",
|
||||
"reference": "2adaefc83df2ec548558307690f376dd7d4f4fce",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/aa6ad8321ed103dc3624fb600a25b66ebf78ec7b",
|
||||
"reference": "aa6ad8321ed103dc3624fb600a25b66ebf78ec7b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4118,7 +4118,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpseclib/phpseclib/issues",
|
||||
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.52"
|
||||
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.50"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -4134,7 +4134,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-04-27T07:02:15+00:00"
|
||||
"time": "2026-03-19T02:57:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pragmarx/google2fa",
|
||||
@@ -6499,16 +6499,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.37.0",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "141046a8f9477948ff284fa65be2095baafb94f2"
|
||||
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2",
|
||||
"reference": "141046a8f9477948ff284fa65be2095baafb94f2",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
|
||||
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -6558,7 +6558,7 @@
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0"
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -6578,20 +6578,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-04-10T16:19:22+00:00"
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-grapheme",
|
||||
"version": "v1.37.0",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
|
||||
"reference": "4864388bfbd3001ce88e234fab652acd91fdc57e"
|
||||
"reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/4864388bfbd3001ce88e234fab652acd91fdc57e",
|
||||
"reference": "4864388bfbd3001ce88e234fab652acd91fdc57e",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70",
|
||||
"reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -6640,7 +6640,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.37.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -6660,11 +6660,11 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-04-26T13:13:48+00:00"
|
||||
"time": "2025-06-27T09:58:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-idn",
|
||||
"version": "v1.37.0",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-idn.git",
|
||||
@@ -6727,7 +6727,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.37.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -6751,7 +6751,7 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-normalizer",
|
||||
"version": "v1.37.0",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
|
||||
@@ -6812,7 +6812,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.37.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -6836,16 +6836,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.37.0",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315"
|
||||
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315",
|
||||
"reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
|
||||
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -6897,7 +6897,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.37.0"
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -6917,20 +6917,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-04-10T17:25:58+00:00"
|
||||
"time": "2024-12-23T08:48:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.37.0",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411"
|
||||
"reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
|
||||
"reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
|
||||
"reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -6981,7 +6981,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0"
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -7001,20 +7001,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-04-10T16:19:22+00:00"
|
||||
"time": "2025-01-02T08:10:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php83",
|
||||
"version": "v1.37.0",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php83.git",
|
||||
"reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149"
|
||||
"reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/3600c2cb22399e25bb226e4a135ce91eeb2a6149",
|
||||
"reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5",
|
||||
"reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -7061,7 +7061,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php83/tree/v1.37.0"
|
||||
"source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -7081,20 +7081,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-04-10T17:25:58+00:00"
|
||||
"time": "2025-07-08T02:45:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php84",
|
||||
"version": "v1.37.0",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php84.git",
|
||||
"reference": "88486db2c389b290bf87ff1de7ebc1e13e42bb06"
|
||||
"reference": "d8ced4d875142b6a7426000426b8abc631d6b191"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/88486db2c389b290bf87ff1de7ebc1e13e42bb06",
|
||||
"reference": "88486db2c389b290bf87ff1de7ebc1e13e42bb06",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191",
|
||||
"reference": "d8ced4d875142b6a7426000426b8abc631d6b191",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -7141,7 +7141,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php84/tree/v1.37.0"
|
||||
"source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -7161,20 +7161,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-04-10T18:47:49+00:00"
|
||||
"time": "2025-06-24T13:30:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php85",
|
||||
"version": "v1.37.0",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php85.git",
|
||||
"reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee"
|
||||
"reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/fcfa4973a9917cef23f2e38774da74a2b7d115ee",
|
||||
"reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91",
|
||||
"reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -7221,7 +7221,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php85/tree/v1.37.0"
|
||||
"source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -7241,20 +7241,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-04-26T13:10:57+00:00"
|
||||
"time": "2025-06-23T16:12:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-uuid",
|
||||
"version": "v1.37.0",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-uuid.git",
|
||||
"reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94"
|
||||
"reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/26dfec253c4cf3e51b541b52ddf7e42cb0908e94",
|
||||
"reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2",
|
||||
"reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -7304,7 +7304,7 @@
|
||||
"uuid"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-uuid/tree/v1.37.0"
|
||||
"source": "https://github.com/symfony/polyfill-uuid/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -7324,7 +7324,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-04-10T16:19:22+00:00"
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
@@ -8285,23 +8285,23 @@
|
||||
},
|
||||
{
|
||||
"name": "voku/portable-ascii",
|
||||
"version": "2.1.1",
|
||||
"version": "2.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/voku/portable-ascii.git",
|
||||
"reference": "8e1051fe39379367aecf014f41744ce7539a856f"
|
||||
"reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/voku/portable-ascii/zipball/8e1051fe39379367aecf014f41744ce7539a856f",
|
||||
"reference": "8e1051fe39379367aecf014f41744ce7539a856f",
|
||||
"url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d",
|
||||
"reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1.0"
|
||||
"php": ">=7.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~8.5 || ~9.6 || ~10.5 || ~11.5"
|
||||
"phpunit/phpunit": "~6.0 || ~7.0 || ~9.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-intl": "Use Intl for transliterator_transliterate() support"
|
||||
@@ -8331,7 +8331,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/voku/portable-ascii/issues",
|
||||
"source": "https://github.com/voku/portable-ascii/tree/2.1.1"
|
||||
"source": "https://github.com/voku/portable-ascii/tree/2.0.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8355,7 +8355,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-04-26T05:33:54+00:00"
|
||||
"time": "2024-11-21T01:49:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "xemlock/htmlpurifier-html5",
|
||||
@@ -8723,16 +8723,16 @@
|
||||
},
|
||||
{
|
||||
"name": "larastan/larastan",
|
||||
"version": "v3.9.6",
|
||||
"version": "v3.9.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/larastan/larastan.git",
|
||||
"reference": "9ad17e83e96b63536cb6ac39c3d40d29ff9cf636"
|
||||
"reference": "64a52bcc5347c89fdf131cb59f96ebfbc8d1ad65"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/larastan/larastan/zipball/9ad17e83e96b63536cb6ac39c3d40d29ff9cf636",
|
||||
"reference": "9ad17e83e96b63536cb6ac39c3d40d29ff9cf636",
|
||||
"url": "https://api.github.com/repos/larastan/larastan/zipball/64a52bcc5347c89fdf131cb59f96ebfbc8d1ad65",
|
||||
"reference": "64a52bcc5347c89fdf131cb59f96ebfbc8d1ad65",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -8746,7 +8746,7 @@
|
||||
"illuminate/pipeline": "^11.44.2 || ^12.4.1 || ^13",
|
||||
"illuminate/support": "^11.44.2 || ^12.4.1 || ^13",
|
||||
"php": "^8.2",
|
||||
"phpstan/phpstan": "^2.1.44"
|
||||
"phpstan/phpstan": "^2.1.32"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^13",
|
||||
@@ -8801,7 +8801,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/larastan/larastan/issues",
|
||||
"source": "https://github.com/larastan/larastan/tree/v3.9.6"
|
||||
"source": "https://github.com/larastan/larastan/tree/v3.9.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8809,7 +8809,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-04-16T10:02:43+00:00"
|
||||
"time": "2026-02-20T12:07:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mockery/mockery",
|
||||
@@ -8956,23 +8956,23 @@
|
||||
},
|
||||
{
|
||||
"name": "nunomaduro/collision",
|
||||
"version": "v8.9.4",
|
||||
"version": "v8.9.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nunomaduro/collision.git",
|
||||
"reference": "716af8f95a470e9094cfca09ed897b023be191a5"
|
||||
"reference": "6eb16883e74fd725ac64dbe81544c961ab448ba5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/716af8f95a470e9094cfca09ed897b023be191a5",
|
||||
"reference": "716af8f95a470e9094cfca09ed897b023be191a5",
|
||||
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/6eb16883e74fd725ac64dbe81544c961ab448ba5",
|
||||
"reference": "6eb16883e74fd725ac64dbe81544c961ab448ba5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"filp/whoops": "^2.18.4",
|
||||
"nunomaduro/termwind": "^2.4.0",
|
||||
"php": "^8.2.0",
|
||||
"symfony/console": "^7.4.8 || ^8.0.8"
|
||||
"symfony/console": "^7.4.8 || ^8.0.4"
|
||||
},
|
||||
"conflict": {
|
||||
"laravel/framework": "<11.48.0 || >=14.0.0",
|
||||
@@ -8980,12 +8980,12 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"brianium/paratest": "^7.8.5",
|
||||
"larastan/larastan": "^3.9.6",
|
||||
"laravel/framework": "^11.48.0 || ^12.56.0 || ^13.5.0",
|
||||
"laravel/pint": "^1.29.1",
|
||||
"orchestra/testbench-core": "^9.12.0 || ^10.12.1 || ^11.2.1",
|
||||
"larastan/larastan": "^3.9.3",
|
||||
"laravel/framework": "^11.48.0 || ^12.56.0 || ^13.2.0",
|
||||
"laravel/pint": "^1.29.0",
|
||||
"orchestra/testbench-core": "^9.12.0 || ^10.12.1 || ^11.0.0",
|
||||
"pestphp/pest": "^3.8.5 || ^4.4.3 || ^5.0.0",
|
||||
"sebastian/environment": "^7.2.1 || ^8.0.4 || ^9.3.0"
|
||||
"sebastian/environment": "^7.2.1 || ^8.0.4 || ^9.0.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
@@ -9048,7 +9048,7 @@
|
||||
"type": "patreon"
|
||||
}
|
||||
],
|
||||
"time": "2026-04-21T14:04:20+00:00"
|
||||
"time": "2026-03-31T21:51:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phar-io/manifest",
|
||||
@@ -9170,11 +9170,11 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "2.1.54",
|
||||
"version": "2.1.46",
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/8be50c3992107dc837b17da4d140fbbdf9a5c5bd",
|
||||
"reference": "8be50c3992107dc837b17da4d140fbbdf9a5c5bd",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/a193923fc2d6325ef4e741cf3af8c3e8f54dbf25",
|
||||
"reference": "a193923fc2d6325ef4e741cf3af8c3e8f54dbf25",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -9219,7 +9219,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-04-29T13:31:09+00:00"
|
||||
"time": "2026-04-01T09:25:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
@@ -10983,5 +10983,5 @@
|
||||
"platform-overrides": {
|
||||
"php": "8.2.0"
|
||||
},
|
||||
"plugin-api-version": "2.9.0"
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
f694c5952272a88d753a9eda40141e227ba7c9a60faf6d0de6952439a3ad8450
|
||||
22e02ee72d21ff719c1073abbec8302f8e2096ba6d072e133051064ed24b45b1
|
||||
|
||||
@@ -78,6 +78,7 @@ return [
|
||||
// Users
|
||||
'users_cannot_delete_only_admin' => 'You cannot delete the only admin',
|
||||
'users_cannot_delete_guest' => 'You cannot delete the guest user',
|
||||
'users_cannot_impersonate' => 'You cannot impersonate this user',
|
||||
'users_could_not_send_invite' => 'Could not create user since invite email failed to send',
|
||||
|
||||
// Roles
|
||||
|
||||
@@ -231,6 +231,11 @@ return [
|
||||
'users_external_auth_id_desc' => 'When an external authentication system is in use (such as SAML2, OIDC or LDAP) this is the ID which links this BookStack user to the authentication system account. You can ignore this field if using the default email-based authentication.',
|
||||
'users_password_warning' => 'Only fill the below if you would like to change the password for this user.',
|
||||
'users_system_public' => 'This user represents any guest users that visit your instance. It cannot be used to log in but is assigned automatically.',
|
||||
'users_impersonate' => 'Impersonate User',
|
||||
'users_impersonate_desc' => 'Log in and browse the application as this user.',
|
||||
'users_impersonate_action' => 'Impersonate',
|
||||
'users_impersonating' => 'Impersonating: :name',
|
||||
'users_impersonate_stop' => 'Stop Impersonating',
|
||||
'users_delete' => 'Delete User',
|
||||
'users_delete_named' => 'Delete user :userName',
|
||||
'users_delete_warning' => 'This will fully delete this user with the name \':userName\' from the system.',
|
||||
|
||||
33
public/dist/app.js
vendored
33
public/dist/app.js
vendored
File diff suppressed because one or more lines are too long
32
public/dist/code.js
vendored
32
public/dist/code.js
vendored
File diff suppressed because one or more lines are too long
1
public/dist/export-styles.css
vendored
1
public/dist/export-styles.css
vendored
File diff suppressed because one or more lines are too long
3
public/dist/legacy-modes.js
vendored
3
public/dist/legacy-modes.js
vendored
File diff suppressed because one or more lines are too long
28
public/dist/markdown.js
vendored
28
public/dist/markdown.js
vendored
File diff suppressed because one or more lines are too long
1
public/dist/styles.css
vendored
1
public/dist/styles.css
vendored
File diff suppressed because one or more lines are too long
32
public/dist/wysiwyg.js
vendored
32
public/dist/wysiwyg.js
vendored
File diff suppressed because one or more lines are too long
@@ -54,6 +54,13 @@
|
||||
@include('layouts.parts.base-body-start')
|
||||
@include('layouts.parts.skip-to-content')
|
||||
@include('layouts.parts.notifications')
|
||||
@if(session('impersonate'))
|
||||
<div style="background-color:#c0392b;color:#fff;text-align:center;padding:8px 16px;font-size:0.9em;">
|
||||
{{ trans('settings.users_impersonating', ['name' => user()->name]) }}
|
||||
|
|
||||
<a href="{{ url('/impersonate/stop') }}" style="color:#fff;text-decoration:underline;">{{ trans('settings.users_impersonate_stop') }}</a>
|
||||
</div>
|
||||
@endif
|
||||
@include('layouts.parts.header')
|
||||
|
||||
<div id="content" components="@yield('content-components')" class="block">
|
||||
|
||||
@@ -39,20 +39,27 @@
|
||||
</li>
|
||||
<li role="presentation"><hr></li>
|
||||
<li>
|
||||
@php
|
||||
$logoutPath = match (config('auth.method')) {
|
||||
'saml2' => '/saml2/logout',
|
||||
'oidc' => '/oidc/logout',
|
||||
default => '/logout',
|
||||
}
|
||||
@endphp
|
||||
<form action="{{ url($logoutPath) }}" method="post">
|
||||
{{ csrf_field() }}
|
||||
<button class="icon-item" role="menuitem" data-shortcut="logout">
|
||||
@if(session('impersonate'))
|
||||
<a href="{{ url('/impersonate/stop') }}" role="menuitem" class="icon-item">
|
||||
@icon('logout')
|
||||
<div>{{ trans('auth.logout') }}</div>
|
||||
</button>
|
||||
</form>
|
||||
<div>{{ trans('settings.users_impersonate_stop') }}</div>
|
||||
</a>
|
||||
@else
|
||||
@php
|
||||
$logoutPath = match (config('auth.method')) {
|
||||
'saml2' => '/saml2/logout',
|
||||
'oidc' => '/oidc/logout',
|
||||
default => '/logout',
|
||||
}
|
||||
@endphp
|
||||
<form action="{{ url($logoutPath) }}" method="post">
|
||||
{{ csrf_field() }}
|
||||
<button class="icon-item" role="menuitem" data-shortcut="logout">
|
||||
@icon('logout')
|
||||
<div>{{ trans('auth.logout') }}</div>
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -103,6 +103,17 @@
|
||||
@endif
|
||||
|
||||
@include('users.api-tokens.parts.list', ['user' => $user, 'context' => 'settings'])
|
||||
|
||||
@if(!$user->isGuest() && $user->id !== user()->id && userCan('users-manage') && !session('impersonate'))
|
||||
<section class="card content-wrap auto-height">
|
||||
<h2 class="list-heading">{{ trans('settings.users_impersonate') }}</h2>
|
||||
<p class="text-muted text-small">{{ trans('settings.users_impersonate_desc') }}</p>
|
||||
<form action="{{ url("/settings/users/{$user->id}/impersonate") }}" method="post">
|
||||
{!! csrf_field() !!}
|
||||
<button type="submit" class="button outline">{{ trans('settings.users_impersonate_action') }}</button>
|
||||
</form>
|
||||
</section>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@stop
|
||||
|
||||
@@ -251,6 +251,8 @@ Route::middleware('auth')->group(function () {
|
||||
Route::get('/settings/users/{id}', [UserControllers\UserController::class, 'edit']);
|
||||
Route::put('/settings/users/{id}', [UserControllers\UserController::class, 'update']);
|
||||
Route::delete('/settings/users/{id}', [UserControllers\UserController::class, 'destroy']);
|
||||
Route::post('/settings/users/{id}/impersonate', [UserControllers\UserController::class, 'impersonate']);
|
||||
Route::get('/impersonate/stop', [UserControllers\UserController::class, 'stopImpersonate']);
|
||||
|
||||
// User Account
|
||||
Route::get('/my-account', [UserControllers\UserAccountController::class, 'redirect']);
|
||||
|
||||
@@ -136,21 +136,17 @@ class EntitySearchTest extends TestCase
|
||||
$page->tags()->saveMany([new Tag(['name' => 'DonkCount', 'value' => '500'])]);
|
||||
$page->created_by = $this->users->admin()->id;
|
||||
$page->save();
|
||||
$otherPage = $this->entities->newPage(['name' => 'A different page in negation tests', 'html' => '<p>A different page in negation tests</p>']);
|
||||
|
||||
$editor = $this->users->editor();
|
||||
$this->actingAs($editor);
|
||||
|
||||
$exactSearch = $this->get('/search?term=' . urlencode('negation -"tortoise"'));
|
||||
$exactSearch->assertStatus(200)->assertDontSeeText($page->name);
|
||||
$exactSearch->assertSeeText($otherPage->name);
|
||||
|
||||
$tagSearchA = $this->get('/search?term=' . urlencode('negation [DonkCount=500]'));
|
||||
$tagSearchA->assertStatus(200)->assertSeeText($page->name);
|
||||
$tagSearchA->assertDontSeeText($otherPage->name);
|
||||
$tagSearchB = $this->get('/search?term=' . urlencode('negation -[DonkCount=500]'));
|
||||
$tagSearchB->assertStatus(200)->assertDontSeeText($page->name);
|
||||
$tagSearchB->assertSeeText($otherPage->name);
|
||||
|
||||
$filterSearchA = $this->get('/search?term=' . urlencode('negation -{created_by:me}'));
|
||||
$filterSearchA->assertStatus(200)->assertSeeText($page->name);
|
||||
|
||||
62
tests/Unit/SsrUrlValidatorTest.php
Normal file
62
tests/Unit/SsrUrlValidatorTest.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use BookStack\Exceptions\HttpFetchException;
|
||||
use BookStack\Util\SsrUrlValidator;
|
||||
use Tests\TestCase;
|
||||
|
||||
class SsrUrlValidatorTest extends TestCase
|
||||
{
|
||||
public function test_allowed()
|
||||
{
|
||||
$testMap = [
|
||||
// Single values
|
||||
['config' => '', 'url' => '', 'result' => false],
|
||||
['config' => '', 'url' => 'https://example.com', 'result' => false],
|
||||
['config' => ' ', 'url' => 'https://example.com', 'result' => false],
|
||||
['config' => '*', 'url' => '', 'result' => false],
|
||||
['config' => '*', 'url' => 'https://example.com', 'result' => true],
|
||||
['config' => 'https://*', 'url' => 'https://example.com', 'result' => true],
|
||||
['config' => 'http://*', 'url' => 'https://example.com', 'result' => false],
|
||||
['config' => 'https://*example.com', 'url' => 'https://example.com', 'result' => true],
|
||||
['config' => 'https://*ample.com', 'url' => 'https://example.com', 'result' => true],
|
||||
['config' => 'https://*.example.com', 'url' => 'https://example.com', 'result' => false],
|
||||
['config' => 'https://*.example.com', 'url' => 'https://test.example.com', 'result' => true],
|
||||
['config' => '*//example.com', 'url' => 'https://example.com', 'result' => true],
|
||||
['config' => '*//example.com', 'url' => 'http://example.com', 'result' => true],
|
||||
['config' => '*//example.co', 'url' => 'http://example.co.uk', 'result' => false],
|
||||
['config' => '*//example.co/bookstack', 'url' => 'https://example.co/bookstack/a/path', 'result' => true],
|
||||
['config' => '*//example.co*', 'url' => 'https://example.co.uk/bookstack/a/path', 'result' => true],
|
||||
['config' => 'https://example.com', 'url' => 'https://example.com/a/b/c?test=cat', 'result' => true],
|
||||
['config' => 'https://example.com', 'url' => 'https://example.co.uk', 'result' => false],
|
||||
|
||||
// Escapes
|
||||
['config' => 'https://(.*?).com', 'url' => 'https://example.com', 'result' => false],
|
||||
['config' => 'https://example.com', 'url' => 'https://example.co.uk#https://example.com', 'result' => false],
|
||||
|
||||
// Multi values
|
||||
['config' => '*//example.org *//example.com', 'url' => 'https://example.com', 'result' => true],
|
||||
['config' => '*//example.org *//example.com', 'url' => 'https://example.com/a/b/c?test=cat#hello', 'result' => true],
|
||||
['config' => '*.example.org *.example.com', 'url' => 'https://example.co.uk', 'result' => false],
|
||||
['config' => ' *.example.org *.example.com ', 'url' => 'https://example.co.uk', 'result' => false],
|
||||
['config' => '* *.example.com', 'url' => 'https://example.co.uk', 'result' => true],
|
||||
['config' => '*//example.org *//example.com *//example.co.uk', 'url' => 'https://example.co.uk', 'result' => true],
|
||||
['config' => '*//example.org *//example.com *//example.co.uk', 'url' => 'https://example.net', 'result' => false],
|
||||
];
|
||||
|
||||
foreach ($testMap as $test) {
|
||||
$result = (new SsrUrlValidator($test['config']))->allowed($test['url']);
|
||||
$this->assertEquals($test['result'], $result, "Failed asserting url '{$test['url']}' with config '{$test['config']}' results " . ($test['result'] ? 'true' : 'false'));
|
||||
}
|
||||
}
|
||||
|
||||
public function test_enssure_allowed()
|
||||
{
|
||||
$result = (new SsrUrlValidator('https://example.com'))->ensureAllowed('https://example.com');
|
||||
$this->assertNull($result);
|
||||
|
||||
$this->expectException(HttpFetchException::class);
|
||||
(new SsrUrlValidator('https://example.com'))->ensureAllowed('https://test.example.com');
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ namespace Tests\Uploads;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Repos\PageRepo;
|
||||
use BookStack\Entities\Tools\TrashCan;
|
||||
use BookStack\Permissions\Permission;
|
||||
use BookStack\Uploads\Attachment;
|
||||
use Tests\TestCase;
|
||||
|
||||
@@ -207,21 +206,6 @@ class AttachmentTest extends TestCase
|
||||
$this->files->deleteAllAttachmentFiles();
|
||||
}
|
||||
|
||||
public function test_attachment_deletion_requires_page_access()
|
||||
{
|
||||
$page = $this->entities->page();
|
||||
$attachment = Attachment::factory()->create(['uploaded_to' => $page->id]);
|
||||
$editor = $this->users->editor();
|
||||
|
||||
$this->permissions->disableEntityInheritedPermissions($page);
|
||||
$this->permissions->grantUserRolePermissions($editor, [Permission::AttachmentDeleteAll]);
|
||||
|
||||
$resp = $this->actingAs($editor)->delete($attachment->getUrl());
|
||||
$resp->assertNotFound();
|
||||
|
||||
$this->assertDatabaseHas('attachments', ['id' => $attachment->id]);
|
||||
}
|
||||
|
||||
public function test_attachment_access_without_permission_shows_404()
|
||||
{
|
||||
$admin = $this->users->admin();
|
||||
|
||||
194
tests/User/UserImpersonationTest.php
Normal file
194
tests/User/UserImpersonationTest.php
Normal file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\User;
|
||||
|
||||
use Tests\TestCase;
|
||||
|
||||
class UserImpersonationTest extends TestCase
|
||||
{
|
||||
protected function tearDown(): void
|
||||
{
|
||||
session()->forget('impersonate');
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function test_impersonate_button_shown_on_edit_page_for_admin()
|
||||
{
|
||||
$viewer = $this->users->viewer();
|
||||
|
||||
$resp = $this->asAdmin()->get("/settings/users/{$viewer->id}");
|
||||
|
||||
$this->withHtml($resp)->assertElementExists("form[action$=\"/settings/users/{$viewer->id}/impersonate\"]");
|
||||
$resp->assertSee('Impersonate User');
|
||||
}
|
||||
|
||||
public function test_impersonate_button_not_shown_for_non_admin()
|
||||
{
|
||||
$viewer = $this->users->viewer();
|
||||
$editor = $this->users->editor();
|
||||
|
||||
$resp = $this->actingAs($editor)->get("/settings/users/{$viewer->id}");
|
||||
|
||||
$resp->assertDontSee('Impersonate User');
|
||||
$this->withHtml($resp)->assertElementNotExists("form[action$=\"/settings/users/{$viewer->id}/impersonate\"]");
|
||||
}
|
||||
|
||||
public function test_impersonate_button_not_shown_for_own_user()
|
||||
{
|
||||
$admin = $this->users->admin();
|
||||
|
||||
$resp = $this->actingAs($admin)->get("/settings/users/{$admin->id}");
|
||||
|
||||
$resp->assertDontSee('Impersonate User');
|
||||
$this->withHtml($resp)->assertElementNotExists("form[action$=\"/settings/users/{$admin->id}/impersonate\"]");
|
||||
}
|
||||
|
||||
public function test_impersonate_button_not_shown_for_guest_user()
|
||||
{
|
||||
$guest = $this->users->guest();
|
||||
|
||||
$resp = $this->asAdmin()->get("/settings/users/{$guest->id}");
|
||||
|
||||
$resp->assertDontSee('Impersonate User');
|
||||
$this->withHtml($resp)->assertElementNotExists("form[action$=\"/settings/users/{$guest->id}/impersonate\"]");
|
||||
}
|
||||
|
||||
public function test_impersonate_button_not_shown_when_already_impersonating()
|
||||
{
|
||||
$viewer = $this->users->viewer();
|
||||
$editor = $this->users->editor();
|
||||
|
||||
$this->asAdmin()->post("/settings/users/{$viewer->id}/impersonate");
|
||||
|
||||
$resp = $this->get("/settings/users/{$editor->id}");
|
||||
$resp->assertDontSee('Impersonate User');
|
||||
}
|
||||
|
||||
public function test_impersonate_sets_session_and_redirects_to_home()
|
||||
{
|
||||
$viewer = $this->users->viewer();
|
||||
|
||||
$resp = $this->asAdmin()->post("/settings/users/{$viewer->id}/impersonate");
|
||||
|
||||
$resp->assertRedirect('/');
|
||||
$this->assertSessionHas('impersonate', $viewer->id);
|
||||
}
|
||||
|
||||
public function test_impersonate_requires_users_manage_permission()
|
||||
{
|
||||
$viewer = $this->users->viewer();
|
||||
$editor = $this->users->editor();
|
||||
|
||||
$resp = $this->actingAs($editor)->post("/settings/users/{$viewer->id}/impersonate");
|
||||
|
||||
$resp->assertRedirect('/');
|
||||
$this->assertSessionMissing('impersonate');
|
||||
}
|
||||
|
||||
public function test_cannot_impersonate_guest_user()
|
||||
{
|
||||
$guest = $this->users->guest();
|
||||
|
||||
$resp = $this->asAdmin()->post("/settings/users/{$guest->id}/impersonate");
|
||||
|
||||
$resp->assertRedirect("/settings/users/{$guest->id}");
|
||||
$this->assertSessionError('You cannot impersonate this user');
|
||||
$this->assertSessionMissing('impersonate');
|
||||
}
|
||||
|
||||
public function test_cannot_impersonate_self()
|
||||
{
|
||||
$admin = $this->users->admin();
|
||||
|
||||
$resp = $this->actingAs($admin)->post("/settings/users/{$admin->id}/impersonate");
|
||||
|
||||
$resp->assertRedirect("/settings/users/{$admin->id}");
|
||||
$this->assertSessionError('You cannot impersonate this user');
|
||||
$this->assertSessionMissing('impersonate');
|
||||
}
|
||||
|
||||
public function test_impersonation_banner_shown_while_impersonating()
|
||||
{
|
||||
$viewer = $this->users->viewer();
|
||||
|
||||
$this->asAdmin()->post("/settings/users/{$viewer->id}/impersonate");
|
||||
$resp = $this->get('/');
|
||||
|
||||
$resp->assertSee('Impersonating: ' . $viewer->name);
|
||||
$resp->assertSee('Stop Impersonating');
|
||||
}
|
||||
|
||||
public function test_impersonation_banner_not_shown_when_not_impersonating()
|
||||
{
|
||||
$resp = $this->asAdmin()->get('/');
|
||||
|
||||
$resp->assertDontSee('Impersonating:');
|
||||
$resp->assertDontSee('Stop Impersonating');
|
||||
}
|
||||
|
||||
public function test_requests_are_performed_as_impersonated_user()
|
||||
{
|
||||
$viewer = $this->users->viewer();
|
||||
$admin = $this->users->admin();
|
||||
|
||||
$this->actingAs($admin)->post("/settings/users/{$viewer->id}/impersonate");
|
||||
|
||||
$resp = $this->get('/');
|
||||
$resp->assertSee('Impersonating: ' . $viewer->name);
|
||||
}
|
||||
|
||||
public function test_stop_impersonate_clears_session_and_redirects_to_user_edit()
|
||||
{
|
||||
$viewer = $this->users->viewer();
|
||||
|
||||
$this->asAdmin()->post("/settings/users/{$viewer->id}/impersonate");
|
||||
$this->assertSessionHas('impersonate', $viewer->id);
|
||||
|
||||
$resp = $this->get('/impersonate/stop');
|
||||
|
||||
$resp->assertRedirect("/settings/users/{$viewer->id}");
|
||||
$this->assertSessionMissing('impersonate');
|
||||
}
|
||||
|
||||
public function test_stop_impersonate_banner_gone_after_stopping()
|
||||
{
|
||||
$viewer = $this->users->viewer();
|
||||
|
||||
$this->asAdmin()->post("/settings/users/{$viewer->id}/impersonate");
|
||||
$this->get('/impersonate/stop');
|
||||
|
||||
$resp = $this->get('/');
|
||||
$resp->assertDontSee('Impersonating:');
|
||||
}
|
||||
|
||||
public function test_middleware_does_not_switch_user_without_impersonate_session()
|
||||
{
|
||||
$admin = $this->users->admin();
|
||||
|
||||
$resp = $this->actingAs($admin)->get('/');
|
||||
|
||||
$resp->assertDontSee('Impersonating:');
|
||||
}
|
||||
|
||||
public function test_middleware_does_not_switch_user_if_actor_lacks_users_manage()
|
||||
{
|
||||
$viewer = $this->users->viewer();
|
||||
$editor = $this->users->editor();
|
||||
|
||||
$this->actingAs($editor)->withSession(['impersonate' => $viewer->id]);
|
||||
|
||||
$resp = $this->get('/');
|
||||
|
||||
$resp->assertDontSee('Impersonating: ' . $viewer->name);
|
||||
}
|
||||
|
||||
public function test_stop_impersonate_link_shown_in_user_menu_while_impersonating()
|
||||
{
|
||||
$viewer = $this->users->viewer();
|
||||
|
||||
$this->asAdmin()->post("/settings/users/{$viewer->id}/impersonate");
|
||||
$resp = $this->get('/');
|
||||
|
||||
$this->withHtml($resp)->assertElementContains('a[href="' . url('/impersonate/stop') . '"]', 'Stop Impersonating');
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Util;
|
||||
|
||||
use BookStack\Exceptions\HttpFetchException;
|
||||
use BookStack\Util\SsrUrlValidator;
|
||||
use Tests\TestCase;
|
||||
|
||||
class SsrUrlValidatorTest extends TestCase
|
||||
{
|
||||
public function test_is_uses_app_config_by_default()
|
||||
{
|
||||
config()->set([
|
||||
'app.ssr_hosts' => 'https://donkey.example.com',
|
||||
]);
|
||||
|
||||
$validator = new SsrUrlValidator();
|
||||
|
||||
$this->assertTrue($validator->allowed('https://donkey.example.com'));
|
||||
$this->assertFalse($validator->allowed('https://monkey.example.com'));
|
||||
}
|
||||
|
||||
public function test_config_string_can_be_passed_in_constructor()
|
||||
{
|
||||
config()->set([
|
||||
'app.ssr_hosts' => 'https://donkey.example.com',
|
||||
]);
|
||||
|
||||
$validator = new SsrUrlValidator('https://monkey.example.com');
|
||||
|
||||
$this->assertFalse($validator->allowed('https://donkey.example.com'));
|
||||
$this->assertTrue($validator->allowed('https://monkey.example.com'));
|
||||
}
|
||||
|
||||
public function test_config_string_can_include_multiple_space_seperated_values()
|
||||
{
|
||||
$validator = new SsrUrlValidator('https://monkey.example.com https://cat.example.com');
|
||||
|
||||
$this->assertFalse($validator->allowed('https://donkey.example.com'));
|
||||
$this->assertTrue($validator->allowed('https://monkey.example.com'));
|
||||
$this->assertTrue($validator->allowed('https://cat.example.com'));
|
||||
}
|
||||
|
||||
public function test_ensure_allowed_throws_if_not_allowed()
|
||||
{
|
||||
$validator = new SsrUrlValidator('https://monkey.example.com');
|
||||
|
||||
$this->assertNull($validator->ensureAllowed('https://monkey.example.com'));
|
||||
|
||||
$this->assertThrows(function () use ($validator) {
|
||||
$validator->ensureAllowed('https://donkey.example.com');
|
||||
}, HttpFetchException::class, 'The URL does not match the configured allowed SSR hosts');
|
||||
}
|
||||
|
||||
public function test_basic_url_matching()
|
||||
{
|
||||
$tests = [
|
||||
// Single values
|
||||
['config' => '', 'url' => '', 'result' => false],
|
||||
['config' => '', 'url' => 'https://example.com', 'result' => false],
|
||||
['config' => ' ', 'url' => 'https://example.com', 'result' => false],
|
||||
['config' => '*', 'url' => '', 'result' => false],
|
||||
['config' => '*', 'url' => 'https://example.com', 'result' => true],
|
||||
['config' => 'https://*', 'url' => 'https://example.com', 'result' => true],
|
||||
['config' => 'http://*', 'url' => 'https://example.com', 'result' => false],
|
||||
['config' => 'https://*example.com', 'url' => 'https://example.com', 'result' => true],
|
||||
['config' => 'https://*ample.com', 'url' => 'https://example.com', 'result' => true],
|
||||
['config' => 'https://*.example.com', 'url' => 'https://example.com', 'result' => false],
|
||||
['config' => 'https://*.example.com', 'url' => 'https://test.example.com', 'result' => true],
|
||||
['config' => '*//example.com', 'url' => 'https://example.com', 'result' => true],
|
||||
['config' => '*//example.com', 'url' => 'http://example.com', 'result' => true],
|
||||
['config' => '*//example.co', 'url' => 'http://example.co.uk', 'result' => false],
|
||||
['config' => '*//example.co/bookstack', 'url' => 'https://example.co/bookstack/a/path', 'result' => true],
|
||||
['config' => '*//example.co*', 'url' => 'https://example.co.uk/bookstack/a/path', 'result' => true],
|
||||
['config' => 'https://example.com', 'url' => 'https://example.com/a/b/c?test=cat', 'result' => true],
|
||||
['config' => 'https://example.com', 'url' => 'https://example.co.uk', 'result' => false],
|
||||
|
||||
// Escapes
|
||||
['config' => 'https://(.*?).com', 'url' => 'https://example.com', 'result' => false],
|
||||
['config' => 'https://example.com', 'url' => 'https://example.co.uk#https://example.com', 'result' => false],
|
||||
|
||||
// Multi values
|
||||
['config' => '*//example.org *//example.com', 'url' => 'https://example.com', 'result' => true],
|
||||
['config' => '*//example.org *//example.com', 'url' => 'https://example.com/a/b/c?test=cat#hello', 'result' => true],
|
||||
['config' => '*.example.org *.example.com', 'url' => 'https://example.co.uk', 'result' => false],
|
||||
['config' => ' *.example.org *.example.com ', 'url' => 'https://example.co.uk', 'result' => false],
|
||||
['config' => '* *.example.com', 'url' => 'https://example.co.uk', 'result' => true],
|
||||
['config' => '*//example.org *//example.com *//example.co.uk', 'url' => 'https://example.co.uk', 'result' => true],
|
||||
['config' => '*//example.org *//example.com *//example.co.uk', 'url' => 'https://example.net', 'result' => false],
|
||||
|
||||
// Further tests
|
||||
['config' => 'https://monkey.example.com', 'url' => 'https://monkey.example.com/a/b', 'result' => true,],
|
||||
['config' => 'https://monkey.example.com', 'url' => 'https://monkey.example.com/a/b?a=b#ab', 'result' => true,],
|
||||
['config' => 'https://monkey.example.com', 'url' => 'https://monkey.example.com:8080/a', 'result' => false,],
|
||||
['config' => '*', 'url' => 'https://a.example.com', 'result' => true,],
|
||||
['config' => 'https://monkey.example.com', 'url' => 'http://monkey.example.com/a/b?a=b#ab', 'result' => false,],
|
||||
['config' => 'https://monkey.example.com', 'url' => 'https://beans.monkey.example.com/a/b?a=b#ab', 'result' => false,],
|
||||
['config' => 'https://*monkey.example.com', 'url' => 'https://amonkey.example.com/a/b?a=b#ab', 'result' => true,],
|
||||
['config' => 'https://*monkey.example.com', 'url' => 'https://donkey.example.com/a/b/monkey.example.com/b?a=b#ab', 'result' => false,],
|
||||
['config' => 'https://monkey.example.com', 'url' => 'https://example.com/monkey.example.com/b?a=monkey.example.com#monkey.example.com', 'result' => false,],
|
||||
['config' => 'https://*.example.com', 'url' => 'https://a.b.example.com/a/b', 'result' => true,],
|
||||
['config' => 'https://*.example.com', 'url' => 'https://a.b.example.a.com/a/b', 'result' => false,],
|
||||
['config' => 'https://*.example.com', 'url' => 'https://a.com/a/b?val=a.example.com', 'result' => false,],
|
||||
['config' => 'https://*.example.com', 'url' => 'https://a.com/a/b#example.com', 'result' => false,],
|
||||
['config' => 'https://a.*.example.com', 'url' => 'https://a.b.c.example.com/c/d', 'result' => true,],
|
||||
['config' => 'https://example.com/webhooks/', 'url' => 'https://example.com/webhooks/beans', 'result' => true,],
|
||||
['config' => 'https://example.com/webhooks/', 'url' => 'https://example.com/a/webhooks/', 'result' => false,],
|
||||
['config' => 'https://example.com:8080', 'url' => 'https://example.com/a/b', 'result' => false,],
|
||||
['config' => 'https://example.com:8080', 'url' => 'https://example.com:8080/a/b', 'result' => true,],
|
||||
['config' => 'https://example.com/*', 'url' => 'https://example.com:8080/a/b', 'result' => false,],
|
||||
];
|
||||
|
||||
foreach ($tests as $testCase) {
|
||||
$validator = new SsrUrlValidator($testCase['config']);
|
||||
$result = $validator->allowed($testCase['url']);
|
||||
$this->assertEquals($testCase['result'], $result, "Failed asserting expected result for config {$testCase['config']} and test value {$testCase['url']}");
|
||||
}
|
||||
}
|
||||
|
||||
public function test_wildcard_does_not_match_userinfo_data_but_still_allows_it()
|
||||
{
|
||||
$validator = new SsrUrlValidator('https://*monkey.example.com');
|
||||
$this->assertFalse($validator->allowed('https://monkey.example.com@a.example.com'));
|
||||
|
||||
$validator = new SsrUrlValidator('https://monkey.example.com*');
|
||||
$this->assertFalse($validator->allowed('https://monkey.example.com@a.example.com'));
|
||||
$this->assertFalse($validator->allowed('https://monkey.example.com:monkey.example.com@a.example.com'));
|
||||
|
||||
$validator = new SsrUrlValidator('https://monkey.example.com');
|
||||
$this->assertTrue($validator->allowed('https://a:b@monkey.example.com'));
|
||||
}
|
||||
|
||||
public function test_percent_encoded_slashes_in_host_are_rejected()
|
||||
{
|
||||
$validator = new SsrUrlValidator('*');
|
||||
|
||||
$this->assertFalse($validator->allowed('https://cat.example.com%2Fa/b'));
|
||||
$this->assertFalse($validator->allowed('https://cat.example.com%2fa/b'));
|
||||
$this->assertFalse($validator->allowed('https://cat%2f.example.com/a/b'));
|
||||
$this->assertFalse($validator->allowed('https://cat.exa%2Fmple.com'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user