fix: whitelisted image url domain matching

Use wildcards on the full url instead of just the hostname.
This commit is contained in:
Roardom
2024-02-28 22:45:40 +00:00
parent 95dba7f1c0
commit f2dbdd324c
10 changed files with 122 additions and 142 deletions
+9 -8
View File
@@ -13,7 +13,7 @@
namespace App\Helpers;
use App\Models\WhitelistedImageDomain;
use App\Models\WhitelistedImageUrl;
class Bbcode
{
@@ -496,15 +496,16 @@ class Bbcode
}
if ($isImage) {
$host = parse_url($url, PHP_URL_HOST);
$whitelistedImageUrls = cache()->rememberForever(
'whitelisted-image-urls',
fn () => WhitelistedImageUrl::query()->pluck('pattern'),
);
if (!\is_string($host)) {
return 'Broken link';
}
$isWhitelisted = $whitelistedImageUrls->contains(function (string $pattern) use ($url) {
$pattern = str_replace('\*', '.*', preg_quote($pattern, '/'));
$whitelistedImageDomains = cache()->rememberForever('whitelisted-image-domains', fn () => WhitelistedImageDomain::query()->pluck('domain'));
$isWhitelisted = $whitelistedImageDomains->firstWhere(fn ($domain) => str_ends_with($host, $domain)) !== null;
return preg_match('/^'.$pattern.'$/i', $url);
});
if (!$isWhitelisted) {
$url = 'https://wsrv.nl/?url='.urlencode($url);
@@ -1,59 +0,0 @@
<?php
/**
* NOTICE OF LICENSE.
*
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
* The details is bundled with this project in the file LICENSE.txt.
*
* @project UNIT3D Community Edition
*
* @author Roardom <roardom@protonmail.com>
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
*/
namespace App\Http\Controllers\Staff;
use App\Http\Controllers\Controller;
use App\Http\Requests\Staff\StoreWhitelistedImageDomainRequest;
use App\Http\Requests\Staff\UpdateWhitelistedImageDomainRequest;
use App\Models\WhitelistedImageDomain;
class WhitelistedImageDomainController extends Controller
{
public function index(): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
{
return view('Staff.whitelisted-image-domain.index', [
'whitelistedImageDomains' => WhitelistedImageDomain::orderBy('domain')->get(),
]);
}
public function update(UpdateWhitelistedImageDomainRequest $request, WhitelistedImageDomain $whitelistedImageDomain): \Illuminate\Http\RedirectResponse
{
$whitelistedImageDomain->update($request->validated());
cache()->forget('whitelisted-image-domains');
return to_route('staff.whitelisted_image_domains.index')
->withSuccess('Domain updated successfully.');
}
public function store(StoreWhitelistedImageDomainRequest $request): \Illuminate\Http\RedirectResponse
{
WhitelistedImageDomain::create($request->validated());
cache()->forget('whitelisted-image-domains');
return to_route('staff.whitelisted_image_domains.index')
->withSuccess('New image domain whitelisted.');
}
public function destroy(WhitelistedImageDomain $whitelistedImageDomain): \Illuminate\Http\RedirectResponse
{
$whitelistedImageDomain->delete();
cache()->forget('whitelisted-image-domains');
return to_route('staff.whitelisted_image_domains.index')
->withSuccess('Domain removed from whitelist.');
}
}
@@ -0,0 +1,59 @@
<?php
/**
* NOTICE OF LICENSE.
*
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
* The details is bundled with this project in the file LICENSE.txt.
*
* @project UNIT3D Community Edition
*
* @author Roardom <roardom@protonmail.com>
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
*/
namespace App\Http\Controllers\Staff;
use App\Http\Controllers\Controller;
use App\Http\Requests\Staff\StoreWhitelistedImageUrlRequest;
use App\Http\Requests\Staff\UpdateWhitelistedImageUrlRequest;
use App\Models\WhitelistedImageUrl;
class WhitelistedImageUrlController extends Controller
{
public function index(): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
{
return view('Staff.whitelisted-image-url.index', [
'whitelistedImageUrls' => WhitelistedImageUrl::orderBy('pattern')->get(),
]);
}
public function update(UpdateWhitelistedImageUrlRequest $request, WhitelistedImageUrl $whitelistedImageUrl): \Illuminate\Http\RedirectResponse
{
$whitelistedImageUrl->update($request->validated());
cache()->forget('whitelisted-image-urls');
return to_route('staff.whitelisted_image_urls.index')
->withSuccess('Image url pattern updated successfully.');
}
public function store(StoreWhitelistedImageUrlRequest $request): \Illuminate\Http\RedirectResponse
{
WhitelistedImageUrl::create($request->validated());
cache()->forget('whitelisted-image-urls');
return to_route('staff.whitelisted_image_urls.index')
->withSuccess('New image url pattern whitelisted.');
}
public function destroy(WhitelistedImageUrl $whitelistedImageUrl): \Illuminate\Http\RedirectResponse
{
$whitelistedImageUrl->delete();
cache()->forget('whitelisted-image-urls');
return to_route('staff.whitelisted_image_urls.index')
->withSuccess('Image url pattern removed from whitelist.');
}
}
@@ -15,7 +15,7 @@ namespace App\Http\Requests\Staff;
use Illuminate\Foundation\Http\FormRequest;
class StoreWhitelistedImageDomainRequest extends FormRequest
class StoreWhitelistedImageUrlRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
@@ -25,11 +25,11 @@ class StoreWhitelistedImageDomainRequest extends FormRequest
public function rules(): array
{
return [
'domain' => [
'pattern' => [
'required',
'string',
'max:255',
'unique:whitelisted_image_domains',
'unique:whitelisted_image_urls',
],
];
}
@@ -15,7 +15,7 @@ namespace App\Http\Requests\Staff;
use Illuminate\Foundation\Http\FormRequest;
class UpdateWhitelistedImageDomainRequest extends FormRequest
class UpdateWhitelistedImageUrlRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
@@ -25,11 +25,11 @@ class UpdateWhitelistedImageDomainRequest extends FormRequest
public function rules(): array
{
return [
'domain' => [
'pattern' => [
'required',
'string',
'max:255',
'unique:whitelisted_image_domains',
'unique:whitelisted_image_urls',
],
];
}
@@ -18,12 +18,12 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* App\Models\WhitelistedImageDomain.
* App\Models\WhitelistedImageUrl.
*
* @property int $id
* @property string $domain
* @property string $pattern
*/
class WhitelistedImageDomain extends Model
class WhitelistedImageUrl extends Model
{
use Auditable;
use HasFactory;
@@ -7,9 +7,9 @@ use Illuminate\Support\Facades\Schema;
return new class () extends Migration {
public function up(): void
{
Schema::create('whitelisted_image_domains', function (Blueprint $table): void {
Schema::create('whitelisted_image_urls', function (Blueprint $table): void {
$table->increments('id');
$table->string('domain')->unique();
$table->string('pattern')->unique();
$table->timestamps();
});
@@ -180,10 +180,10 @@
<p class="form__group form__group--horizontal">
<a
class="form__button form__button--text"
href="{{ route('staff.whitelisted_image_domains.index') }}"
href="{{ route('staff.whitelisted_image_urls.index') }}"
>
<i class="{{ config('other.font-awesome') }} fa-globe"></i>
Whitelisted Image Domains
Whitelisted Image URLs
</a>
</p>
<p class="form__group form__group--horizontal">
@@ -6,15 +6,15 @@
{{ __('staff.staff-dashboard') }}
</a>
</li>
<li class="breadcrumb--active">Whitelisted Image Domains</li>
<li class="breadcrumb--active">Whitelisted Image URLs</li>
@endsection
@section('page', 'page__whitelisted-image-domains--index')
@section('page', 'page__whitelisted-image-urls--index')
@section('main')
<section class="panelV2">
<header class="panel__header">
<h2 class="panel__heading">Whitelisted Image Domains</h2>
<h2 class="panel__heading">Whitelisted Image URLs</h2>
<div class="panel__actions">
<div class="panel__action" x-data="dialog">
<button class="form__button form__button--text" x-bind="showDialog">
@@ -25,21 +25,21 @@
<form
class="dialog__form"
method="POST"
action="{{ route('staff.whitelisted_image_domains.store') }}"
action="{{ route('staff.whitelisted_image_urls.store') }}"
x-bind="dialogForm"
>
@csrf
<p class="form__group">
<input
id="domain"
id="pattern"
class="form__text"
name="domain"
name="pattern"
placeholder=" "
required
type="text"
/>
<label class="form__label form__label--floating" for="domain">
Domain
<label class="form__label form__label--floating" for="pattern">
URL pattern
</label>
</p>
<p class="form__group">
@@ -63,30 +63,30 @@
<table class="data-table">
<thead>
<th>ID</th>
<th>Domain</th>
<th>URL Pattern</th>
<th>{{ __('common.created_at') }}</th>
<th>{{ __('forum.updated-at') }}</th>
<th>{{ __('common.actions') }}</th>
</thead>
<tbody>
@forelse ($whitelistedImageDomains as $whitelistedImageDomain)
@forelse ($whitelistedImageUrls as $whitelistedImageUrl)
<tr>
<td>{{ $whitelistedImageDomain->id }}</td>
<td>{{ $whitelistedImageDomain->domain }}</td>
<td>{{ $whitelistedImageUrl->id }}</td>
<td>{{ $whitelistedImageUrl->pattern }}</td>
<td>
<time
datetime="{{ $whitelistedImageDomain->created_at }}"
title="{{ $whitelistedImageDomain->created_at }}"
datetime="{{ $whitelistedImageUrl->created_at }}"
title="{{ $whitelistedImageUrl->created_at }}"
>
{{ $whitelistedImageDomain->created_at }}
{{ $whitelistedImageUrl->created_at }}
</time>
</td>
<td>
<time
datetime="{{ $whitelistedImageDomain->updated_at }}"
title="{{ $whitelistedImageDomain->updated_at }}"
datetime="{{ $whitelistedImageUrl->updated_at }}"
title="{{ $whitelistedImageUrl->updated_at }}"
>
{{ $whitelistedImageDomain->updated_at }}
{{ $whitelistedImageUrl->updated_at }}
</time>
</td>
<td>
@@ -105,26 +105,26 @@
<form
class="dialog__form"
method="POST"
action="{{ route('staff.whitelisted_image_domains.update', ['whitelistedImageDomain' => $whitelistedImageDomain]) }}"
action="{{ route('staff.whitelisted_image_urls.update', ['whitelistedImageUrl' => $whitelistedImageUrl]) }}"
x-bind="dialogForm"
>
@csrf
@method('PATCH')
<p class="form__group">
<input
id="domain"
id="pattern"
class="form__text"
name="domain"
name="pattern"
placeholder=" "
required
type="text"
value="{{ $whitelistedImageDomain->domain }}"
value="{{ $whitelistedImageUrl->pattern }}"
/>
<label
class="form__label form__label--floating"
for="domain"
for="pattern"
>
{{ __('common.position') }}
URL Pattern
</label>
</p>
<p class="form__group">
@@ -146,7 +146,7 @@
</li>
<li class="data-table__action">
<form
action="{{ route('staff.whitelisted_image_domains.destroy', ['whitelistedImageDomain' => $whitelistedImageDomain]) }}"
action="{{ route('staff.whitelisted_image_urls.destroy', ['whitelistedImageUrl' => $whitelistedImageUrl]) }}"
method="POST"
x-data="confirmation"
>
@@ -155,7 +155,7 @@
<button
x-on:click.prevent="confirmAction"
class="form__button form__button--text"
data-b64-deletion-message="{{ base64_encode('Are you sure you want to remove this whitelisted image domain: ' . $whitelistedImageDomain->domain . '?') }}"
data-b64-deletion-message="{{ base64_encode('Are you sure you want to remove this whitelisted image url: ' . $whitelistedImageUrl->pattern . '?') }}"
>
{{ __('common.delete') }}
</button>
@@ -166,7 +166,7 @@
</tr>
@empty
<tr>
<td colspan="2">No whitelisted image domains.</td>
<td colspan="2">No whitelisted image urls.</td>
</tr>
@endforelse
</tbody>
@@ -181,36 +181,15 @@
<div class="panel__body">
<p>
When users add images via BBCode, other users will load the image on page load. This
means whoever operates the image domain can view the connecting IPs. Therefore, all
images entered via BBCode are proxied.
means whoever operates the website of the image url can view the connecting IPs.
Therefore, all images entered via BBCode are proxied.
</p>
<p>
In exception cases where the proxy blocks a popular image host, that image host
domain should be whitelisted here. Any trusted image domains can also be included
here to increase client image loading speeds.
</p>
</div>
</section>
<section class="panelV2">
<h2 class="panel__heading">Warning</h2>
<div class="panel__body">
<p>
Note: if you whitelist
<code>xyz.com</code>
, it will also whitelist
<code>abcxyz.com</code>
since they share the same suffix. To prevent this, whitelist
<code>https://xyz.com</code>
instead.
</p>
<p>
Alternatively, if you wish to allow all subdomains of
<code>xyz.com</code>
, e.g.
<code>image1.xyz.com</code>
, then whitelist
<code>.xyz.com</code>
.
In exception cases where the proxy blocks a popular image host, that image url
should be whitelisted here. Any trusted image urls can also be included here to
increase client image loading speeds. You can use
<code>*</code>
as a wildcard when matching urls.
</p>
</div>
</section>
+7 -7
View File
@@ -1106,13 +1106,13 @@ Route::middleware('language')->group(function (): void {
});
});
// Whitelisted Image Domains
Route::prefix('whitelisted-image-domains')->group(function (): void {
Route::name('whitelisted_image_domains.')->group(function (): void {
Route::get('/', [App\Http\Controllers\Staff\WhitelistedImageDomainController::class, 'index'])->name('index');
Route::post('/store', [App\Http\Controllers\Staff\WhitelistedImageDomainController::class, 'store'])->name('store');
Route::patch('/{whitelistedImageDomain}/update', [App\Http\Controllers\Staff\WhitelistedImageDomainController::class, 'update'])->name('update');
Route::delete('/{whitelistedImageDomain}/destroy', [App\Http\Controllers\Staff\WhitelistedImageDomainController::class, 'destroy'])->name('destroy');
// Whitelisted Image URL Patterns
Route::prefix('whitelisted-image-urls')->group(function (): void {
Route::name('whitelisted_image_urls.')->group(function (): void {
Route::get('/', [App\Http\Controllers\Staff\WhitelistedImageUrlController::class, 'index'])->name('index');
Route::post('/store', [App\Http\Controllers\Staff\WhitelistedImageUrlController::class, 'store'])->name('store');
Route::patch('/{whitelistedImageUrl}/update', [App\Http\Controllers\Staff\WhitelistedImageUrlController::class, 'update'])->name('update');
Route::delete('/{whitelistedImageUrl}/destroy', [App\Http\Controllers\Staff\WhitelistedImageUrlController::class, 'destroy'])->name('destroy');
});
});