Compare commits

..

14 Commits

Author SHA1 Message Date
Dan Brown
3ddfa9b948 Meta: Updated security info and fixed some tests/links 2026-04-30 00:32:27 +01:00
Dan Brown
55317039ac Meta: Converted GitHub references in codebase to Codeberg 2026-04-28 09:30:48 +01:00
Dan Brown
24e6087ef8 Meta: Updated readme shields and fixed workflow value 2026-04-27 21:13:05 +01:00
Dan Brown
7c1d30bc8f Translations: Added crowdin workflow action 2026-04-27 20:56:05 +01:00
Dan Brown
c1610c4532 Meta: Migrated repo content to forgejo
Kept some GitHub templates with warnings about the migration.
Made some initial updates to readme for the migration.
2026-04-27 17:48:27 +01:00
Dan Brown
2e2f59fa0f CI: Updated images to debian trixie 2026-04-27 13:36:47 +01:00
Dan Brown
cc6e9e0546 CI: Attempt a more robust avif support check 2026-04-27 13:17:58 +01:00
Dan Brown
0f59981932 CI: Updated tests using DB to set test DB URL 2026-04-27 12:52:05 +01:00
Dan Brown
a37f903dc7 CI: Migrated workflows to forgejo 2026-04-27 12:10:44 +01:00
Dan Brown
74aa897626 Readme: Updated netways sponsor link 2026-04-24 23:16:44 +01:00
Dan Brown
4b624596c8 Merge pull request #6109 from BookStackApp/dompdf_font_loading
PDF: Started building system to allow custom DOMPDF font loading
2026-04-22 13:30:48 +01:00
Dan Brown
00239bb6c8 Exports: Improved dompdf font loading permission errors 2026-04-22 13:22:20 +01:00
Dan Brown
241563e8fc Exports: Added testing coverage for DOMPDF font usage 2026-04-22 13:12:34 +01:00
Dan Brown
e91747785b PDF: Started building system to allow custom DOMPDF font loading 2026-04-20 15:42:28 +01:00
51 changed files with 337 additions and 263 deletions

View File

@@ -0,0 +1,2 @@
Please find our community rules on our website here:
https://www.bookstackapp.com/about/community-rules/

4
.forgejo/FUNDING.yml Normal file
View File

@@ -0,0 +1,4 @@
# These are supported funding model platforms
github: [ssddanbrown]
ko_fi: ssddanbrown

View File

@@ -0,0 +1,13 @@
blank_issues_enabled: false
contact_links:
- name: Community Forum Support
url: https://community.bookstackapp.com
about: Get support by talking with the BookStack team & community.
- name: Debugging & Common Issues
url: https://www.bookstackapp.com/docs/admin/debugging/
about: Find details on how to debug issues and view common issues with their resolutions.
- name: Official Support Plans
url: https://www.bookstackapp.com/support/
about: View our official support plans that offer assured support for business.

View File

@@ -33,7 +33,7 @@ body:
attributes: attributes:
label: Have you searched for an existing open/closed issue? label: Have you searched for an existing open/closed issue?
description: | description: |
To help us keep these issues under control, please ensure you have first [searched our issue list](https://github.com/BookStackApp/BookStack/issues?q=is%3Aissue) for any existing issues that cover the fundamental benefit/goal of your request. To help us keep these issues under control, please ensure you have first [searched our issue list](https://codeberg.org/bookstack/bookstack/issues) for any existing issues that cover the fundamental benefit/goal of your request.
options: options:
- label: I have searched for existing issues and none cover my fundamental request - label: I have searched for existing issues and none cover my fundamental request
required: true required: true

View File

@@ -15,11 +15,11 @@ body:
- type: checkboxes - type: checkboxes
id: searchissue id: searchissue
attributes: attributes:
label: Searched GitHub Issues label: Searched Existing Issues
description: | description: |
I have searched for the issue and potential resolutions within the [project's GitHub issue list](https://github.com/BookStackApp/BookStack/issues) I have searched for the issue and potential resolutions within the [project's issue list](https://codeberg.org/bookstack/bookstack/issues)
options: options:
- label: I have searched GitHub for the issue. - label: I have searched for the issue.
required: true required: true
- type: textarea - type: textarea
id: scenario id: scenario

View File

@@ -2,7 +2,7 @@
## Supported Versions ## Supported Versions
Only the [latest version](https://github.com/BookStackApp/BookStack/releases) of BookStack is supported. Only the [latest version](https://codeberg.org/bookstack/bookstack/releases) of BookStack is supported.
We generally don't support older versions of BookStack due to maintenance effort and We generally don't support older versions of BookStack due to maintenance effort and
since we aim to provide a fairly stable upgrade path for new versions. since we aim to provide a fairly stable upgrade path for new versions.
@@ -12,13 +12,11 @@ If you'd like to be notified of new potential security concerns you can [sign-up
## Reporting a Vulnerability ## Reporting a Vulnerability
If you've found an issue that likely has no impact to existing users (For example, in a development-only branch) If you've found an issue that likely has no impact to existing users (For example, an issue only in the development branch)
feel free to raise it via a standard GitHub bug report issue. feel free to raise it via a standard Codeberg bug report issue.
If the issue could have a security impact to BookStack instances, If the issue could have a security impact to BookStack instances,
please directly contact the lead maintainer [@ssddanbrown](https://github.com/ssddanbrown). please directly contact the lead maintainer via email Dan Brown using the [details found here](https://www.bookstackapp.com/links/contact/).
You will need to log in to be able to see the email address on the [GitHub profile page](https://github.com/ssddanbrown).
Alternatively you can send a DM via Mastodon to [@danb@fosstodon.org](https://fosstodon.org/@danb).
Please be patient while the vulnerability is being reviewed. Deploying the fix to address the vulnerability Please be patient while the vulnerability is being reviewed. Deploying the fix to address the vulnerability
can often take a little time due to the amount of preparation required, to ensure the vulnerability has can often take a little time due to the amount of preparation required, to ensure the vulnerability has

View File

@@ -0,0 +1,11 @@
## Details
<!-- Write details of your pull request in here -->
<!-- Include references to any relevant issues/discussions -->
## Checklist
<!-- Put an 'x' in between the brackets below to confirm these elements -->
- [ ] I have read the [BookStack community rules](https://www.bookstackapp.com/about/community-rules/).
- [ ] This PR does not feature significant use of LLM/AI generation as per the community rules above.

View File

@@ -1,6 +1,7 @@
name: analyse-php name: analyse-php
on: on:
workflow_dispatch:
push: push:
paths: paths:
- '**.php' - '**.php'
@@ -11,14 +12,16 @@ on:
jobs: jobs:
build: build:
if: ${{ github.ref != 'refs/heads/l10n_development' }} if: ${{ github.ref != 'refs/heads/l10n_development' }}
runs-on: ubuntu-24.04 runs-on: docker
container:
image: docker.io/library/node:24-trixie
steps: steps:
- uses: actions/checkout@v4 - uses: https://code.forgejo.org/actions/checkout@v6
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: https://github.com/shivammathur/setup-php@v2
with: with:
php-version: 8.3 php-version: 8.5
extensions: gd, mbstring, json, curl, xml, mysql, ldap extensions: gd, mbstring, json, curl, xml, mysql, ldap
- name: Get Composer Cache Directory - name: Get Composer Cache Directory
@@ -27,14 +30,16 @@ jobs:
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer packages - name: Cache composer packages
uses: actions/cache@v4 uses: https://code.forgejo.org/actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-8.3 key: ${{ runner.os }}-composer-8.5
restore-keys: ${{ runner.os }}-composer- restore-keys: ${{ runner.os }}-composer-
- name: Install composer dependencies - name: Install composer dependencies
run: composer install --prefer-dist --no-interaction --ansi run: composer install --prefer-dist --no-interaction --ansi
env:
COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.GH_TOKEN }}"}}'
- name: Run static analysis check - name: Run static analysis check
run: composer check-static run: composer check-static

View File

@@ -1,6 +1,7 @@
name: lint-js name: lint-js
on: on:
workflow_dispatch:
push: push:
paths: paths:
- '**.js' - '**.js'
@@ -13,9 +14,11 @@ on:
jobs: jobs:
build: build:
if: ${{ github.ref != 'refs/heads/l10n_development' }} if: ${{ github.ref != 'refs/heads/l10n_development' }}
runs-on: ubuntu-24.04 runs-on: docker
container:
image: docker.io/library/node:24-trixie
steps: steps:
- uses: actions/checkout@v4 - uses: https://code.forgejo.org/actions/checkout@v6
- name: Install NPM deps - name: Install NPM deps
run: npm ci run: npm ci

View File

@@ -1,6 +1,7 @@
name: lint-php name: lint-php
on: on:
workflow_dispatch:
push: push:
paths: paths:
- '**.php' - '**.php'
@@ -11,14 +12,16 @@ on:
jobs: jobs:
build: build:
if: ${{ github.ref != 'refs/heads/l10n_development' }} if: ${{ github.ref != 'refs/heads/l10n_development' }}
runs-on: ubuntu-24.04 runs-on: docker
container:
image: docker.io/library/node:24-trixie
steps: steps:
- uses: actions/checkout@v4 - uses: https://code.forgejo.org/actions/checkout@v6
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: https://github.com/shivammathur/setup-php@v2
with: with:
php-version: 8.3 php-version: 8.5
tools: phpcs tools: phpcs
- name: Run formatting check - name: Run formatting check

View File

@@ -0,0 +1,33 @@
name: Crowdin Action
on:
push:
branches: [ development ]
paths:
- 'lang/**.php'
schedule:
- cron: '30 4 * * *'
workflow_dispatch:
jobs:
synchronize-with-crowdin:
runs-on: docker
container:
image: docker.io/library/node:24-trixie
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v6
- name: crowdin action
uses: https://github.com/crowdin/github-action@v2
with:
upload_sources: true
upload_translations: false
download_translations: true
localization_branch_name: l10n_development
create_pull_request: false
github_base_url: codeberg.org
env:
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}

View File

@@ -1,6 +1,7 @@
name: test-js name: test-js
on: on:
workflow_dispatch:
push: push:
paths: paths:
- '**.js' - '**.js'
@@ -15,9 +16,11 @@ on:
jobs: jobs:
build: build:
if: ${{ github.ref != 'refs/heads/l10n_development' }} if: ${{ github.ref != 'refs/heads/l10n_development' }}
runs-on: ubuntu-24.04 runs-on: docker
container:
image: docker.io/library/node:24-trixie
steps: steps:
- uses: actions/checkout@v6 - uses: https://code.forgejo.org/actions/checkout@v6
- name: Install NPM deps - name: Install NPM deps
run: npm ci run: npm ci

View File

@@ -1,6 +1,7 @@
name: test-migrations name: test-migrations
on: on:
workflow_dispatch:
push: push:
paths: paths:
- '**.php' - '**.php'
@@ -13,15 +14,25 @@ on:
jobs: jobs:
build: build:
if: ${{ github.ref != 'refs/heads/l10n_development' }} if: ${{ github.ref != 'refs/heads/l10n_development' }}
runs-on: ubuntu-24.04 runs-on: docker
container:
image: docker.io/library/node:24-trixie
strategy: strategy:
matrix: matrix:
php: ['8.2', '8.3', '8.4', '8.5'] php: ['8.2', '8.3', '8.4', '8.5']
services:
mysql:
image: docker.io/library/mariadb:12.2.2-noble
env:
MARIADB_USER: bookstack-test
MARIADB_PASSWORD: bookstack-test
MARIADB_DATABASE: bookstack-test
MARIADB_ROOT_PASSWORD: password
steps: steps:
- uses: actions/checkout@v4 - uses: https://code.forgejo.org/actions/checkout@v6
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: https://github.com/shivammathur/setup-php@v2
with: with:
php-version: ${{ matrix.php }} php-version: ${{ matrix.php }}
extensions: gd, mbstring, json, curl, xml, mysql, ldap extensions: gd, mbstring, json, curl, xml, mysql, ldap
@@ -32,34 +43,31 @@ jobs:
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer packages - name: Cache composer packages
uses: actions/cache@v4 uses: https://code.forgejo.org/actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ matrix.php }} key: ${{ runner.os }}-composer-${{ matrix.php }}
restore-keys: ${{ runner.os }}-composer- restore-keys: ${{ runner.os }}-composer-
- name: Start MySQL
run: |
sudo systemctl start mysql
- name: Create database & user
run: |
mysql -uroot -proot -e 'CREATE DATABASE IF NOT EXISTS `bookstack-test`;'
mysql -uroot -proot -e "CREATE USER 'bookstack-test'@'localhost' IDENTIFIED WITH mysql_native_password BY 'bookstack-test';"
mysql -uroot -proot -e "GRANT ALL ON \`bookstack-test\`.* TO 'bookstack-test'@'localhost';"
mysql -uroot -proot -e 'FLUSH PRIVILEGES;'
- name: Install composer dependencies - name: Install composer dependencies
run: composer install --prefer-dist --no-interaction --ansi run: composer install --prefer-dist --no-interaction --ansi
env:
COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.GH_TOKEN }}"}}'
- name: Start migration test - name: Start migration test
env:
TEST_DATABASE_URL: 'mysql://bookstack-test:bookstack-test@mysql/bookstack-test'
run: | run: |
php${{ matrix.php }} artisan migrate --force -n --database=mysql_testing php${{ matrix.php }} artisan migrate --force -n --database=mysql_testing
- name: Start migration:rollback test - name: Start migration:rollback test
env:
TEST_DATABASE_URL: 'mysql://bookstack-test:bookstack-test@mysql/bookstack-test'
run: | run: |
php${{ matrix.php }} artisan migrate:rollback --force -n --database=mysql_testing php${{ matrix.php }} artisan migrate:rollback --force -n --database=mysql_testing
- name: Start migration rerun test - name: Start migration rerun test
env:
TEST_DATABASE_URL: 'mysql://bookstack-test:bookstack-test@mysql/bookstack-test'
run: | run: |
php${{ matrix.php }} artisan migrate --force -n --database=mysql_testing php${{ matrix.php }} artisan migrate --force -n --database=mysql_testing

View File

@@ -1,6 +1,7 @@
name: test-php name: test-php
on: on:
workflow_dispatch:
push: push:
paths: paths:
- '**.php' - '**.php'
@@ -13,15 +14,25 @@ on:
jobs: jobs:
build: build:
if: ${{ github.ref != 'refs/heads/l10n_development' }} if: ${{ github.ref != 'refs/heads/l10n_development' }}
runs-on: ubuntu-24.04 runs-on: docker
container:
image: docker.io/library/node:24-trixie
strategy: strategy:
matrix: matrix:
php: ['8.2', '8.3', '8.4', '8.5'] php: ['8.2', '8.3', '8.4', '8.5']
services:
mysql:
image: docker.io/library/mariadb:12.2.2-noble
env:
MARIADB_USER: bookstack-test
MARIADB_PASSWORD: bookstack-test
MARIADB_DATABASE: bookstack-test
MARIADB_ROOT_PASSWORD: password
steps: steps:
- uses: actions/checkout@v4 - uses: https://code.forgejo.org/actions/checkout@v6
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: https://github.com/shivammathur/setup-php@v2
with: with:
php-version: ${{ matrix.php }} php-version: ${{ matrix.php }}
extensions: gd, mbstring, json, curl, xml, mysql, ldap, gmp extensions: gd, mbstring, json, curl, xml, mysql, ldap, gmp
@@ -32,30 +43,25 @@ jobs:
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer packages - name: Cache composer packages
uses: actions/cache@v4 uses: https://code.forgejo.org/actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ matrix.php }} key: ${{ runner.os }}-composer-${{ matrix.php }}
restore-keys: ${{ runner.os }}-composer- restore-keys: ${{ runner.os }}-composer-
- name: Start Database
run: |
sudo systemctl start mysql
- name: Setup Database
run: |
mysql -uroot -proot -e 'CREATE DATABASE IF NOT EXISTS `bookstack-test`;'
mysql -uroot -proot -e "CREATE USER 'bookstack-test'@'localhost' IDENTIFIED WITH mysql_native_password BY 'bookstack-test';"
mysql -uroot -proot -e "GRANT ALL ON \`bookstack-test\`.* TO 'bookstack-test'@'localhost';"
mysql -uroot -proot -e 'FLUSH PRIVILEGES;'
- name: Install composer dependencies - name: Install composer dependencies
run: composer install --prefer-dist --no-interaction --ansi run: composer install --prefer-dist --no-interaction --ansi
env:
COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.GH_TOKEN }}"}}'
- name: Migrate and seed the database - name: Migrate and seed the database
env:
TEST_DATABASE_URL: 'mysql://bookstack-test:bookstack-test@mysql/bookstack-test'
run: | run: |
php${{ matrix.php }} artisan migrate --force -n --database=mysql_testing php${{ matrix.php }} artisan migrate --force -n --database=mysql_testing
php${{ matrix.php }} artisan db:seed --force -n --class=DummyContentSeeder --database=mysql_testing php${{ matrix.php }} artisan db:seed --force -n --class=DummyContentSeeder --database=mysql_testing
- name: Run PHP tests - name: Run PHP tests
env:
TEST_DATABASE_URL: 'mysql://bookstack-test:bookstack-test@mysql/bookstack-test'
run: php${{ matrix.php }} ./vendor/bin/phpunit run: php${{ matrix.php }} ./vendor/bin/phpunit

View File

@@ -1,8 +1,8 @@
blank_issues_enabled: false blank_issues_enabled: false
contact_links: contact_links:
- name: Discord Chat Support - name: Open Issues Here Instead
url: https://discord.gg/ztkBqR2 url: https://codeberg.org/bookstack/bookstack/issues
about: Realtime support & chat with the BookStack community and the team. about: This project has migrated to Codeberg, please open issues there instead.
- name: Debugging & Common Issues - name: Debugging & Common Issues
url: https://www.bookstackapp.com/docs/admin/debugging/ url: https://www.bookstackapp.com/docs/admin/debugging/

View File

@@ -1,11 +1,10 @@
## Details **Warning:**
<!-- Write details of your pull request in here --> This project has migrated to Codeberg:
<!-- Include references to any relevant issues/discussions --> https://codeberg.org/bookstack/bookstack
## Checklist Please open pull requests here instead.
<!-- Put an 'x' in between the brackets below to confirm these elements --> ANY PULL REQUESTS OPENED HERE WILL BE CLOSED WITHOUT COMMENT OR MERGE.
- [ ] I have read the [BookStack community rules](https://www.bookstackapp.com/about/community-rules/). ---
- [ ] This PR does not feature significant use of LLM/AI generation as per the community rules above.

View File

@@ -55,7 +55,6 @@ class OidcController extends Controller
} }
try { try {
$this->throwIfAuthorizationError($request);
$this->oidcService->processAuthorizeResponse($request->query('code')); $this->oidcService->processAuthorizeResponse($request->query('code'));
} catch (OidcException $oidcException) { } catch (OidcException $oidcException) {
$this->showErrorNotification($oidcException->getMessage()); $this->showErrorNotification($oidcException->getMessage());
@@ -73,23 +72,4 @@ class OidcController extends Controller
{ {
return redirect($this->oidcService->logout()); return redirect($this->oidcService->logout());
} }
/**
*
* @throws OidcException
*/
private function throwIfAuthorizationError(Request $request): void
{
$errorCode = $request->query('error');
if (!$errorCode) {
return;
}
$errorDescription = $request->query('error_description');
if ($errorDescription) {
throw new OidcException($errorDescription);
}
throw new OidcException(trans('errors.oidc_fail_authed', ['system' => config('oidc.name')]));
}
} }

View File

@@ -1,27 +0,0 @@
<?php
namespace BookStack\Access\Oidc;
use League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider;
/**
* Option provider that sends credentials via HTTP Basic Auth header
* and also includes client_id in the request body.
*/
class OidcHttpBasicWithClientIdOptionProvider extends HttpBasicAuthOptionProvider
{
public function getAccessTokenOptions($method, array $params)
{
$clientId = $params['client_id'] ?? null;
$options = parent::getAccessTokenOptions($method, $params);
if ($clientId) {
parse_str($options['body'] ?? '', $body);
$body['client_id'] = $clientId;
$options['body'] = http_build_query($body);
}
return $options;
}
}

View File

@@ -9,10 +9,10 @@ class OidcIdToken extends OidcJwtWithClaims implements ProvidesClaims
* *
* @throws OidcInvalidTokenException * @throws OidcInvalidTokenException
*/ */
public function validate(OidcProviderSettings $settings): bool public function validate(string $clientId): bool
{ {
parent::validateCommonTokenDetails($settings); parent::validateCommonTokenDetails($clientId);
$this->validateTokenClaims($settings->clientId); $this->validateTokenClaims($clientId);
return true; return true;
} }

View File

@@ -9,9 +9,7 @@ class OidcJwtWithClaims implements ProvidesClaims
protected string $signature; protected string $signature;
protected string $issuer; protected string $issuer;
protected array $tokenParts = []; protected array $tokenParts = [];
protected array $acceptedSignatures = [self::hs256Signature, self::rs256Signature];
private const hs256Signature = 'HS256'
, rs256Signature = 'RS256';
/** /**
* @var array[]|string[] * @var array[]|string[]
*/ */
@@ -61,11 +59,11 @@ class OidcJwtWithClaims implements ProvidesClaims
* *
* @throws OidcInvalidTokenException * @throws OidcInvalidTokenException
*/ */
public function validateCommonTokenDetails(OidcProviderSettings $settings): bool public function validateCommonTokenDetails(string $clientId): bool
{ {
$this->validateTokenStructure(); $this->validateTokenStructure();
$this->validateTokenSignature($settings); $this->validateTokenSignature();
$this->validateCommonClaims($settings->clientId); $this->validateCommonClaims($clientId);
return true; return true;
} }
@@ -104,12 +102,12 @@ class OidcJwtWithClaims implements ProvidesClaims
protected function validateTokenStructure(): void protected function validateTokenStructure(): void
{ {
foreach (['header', 'payload'] as $prop) { foreach (['header', 'payload'] as $prop) {
if (empty($this->$prop) || !is_array($this->$prop)) { if (empty($this->$prop)) {
throw new OidcInvalidTokenException("Could not parse out a valid {$prop} within the provided token"); throw new OidcInvalidTokenException("Could not parse out a valid {$prop} within the provided token");
} }
} }
if (empty($this->signature) || !is_string($this->signature)) { if (empty($this->signature)) {
throw new OidcInvalidTokenException('Could not parse out a valid signature within the provided token'); throw new OidcInvalidTokenException('Could not parse out a valid signature within the provided token');
} }
} }
@@ -119,10 +117,12 @@ class OidcJwtWithClaims implements ProvidesClaims
* *
* @throws OidcInvalidTokenException * @throws OidcInvalidTokenException
*/ */
protected function validateTokenSignature(OidcProviderSettings $settings): void { protected function validateTokenSignature(): void
$validSignatures = implode(', ',$this->acceptedSignatures); {
switch ($this->header['alg']) { if ($this->header['alg'] !== 'RS256') {
case self::rs256Signature: throw new OidcInvalidTokenException("Only RS256 signature validation is supported. Token reports using {$this->header['alg']}");
}
$parsedKeys = array_map(function ($key) { $parsedKeys = array_map(function ($key) {
try { try {
return new OidcJwtSigningKey($key); return new OidcJwtSigningKey($key);
@@ -142,19 +142,6 @@ class OidcJwtWithClaims implements ProvidesClaims
} }
throw new OidcInvalidTokenException('Token signature could not be validated using the provided keys'); throw new OidcInvalidTokenException('Token signature could not be validated using the provided keys');
case self::hs256Signature:
$secret = $settings->clientSecret;
$contentToSign = $this->tokenParts[0] . '.' . $this->tokenParts[1];
$expectedSignature = hash_hmac('sha256', $contentToSign, $secret, true);
if (hash_equals($expectedSignature, $this->signature)) {
return;
}
throw new OidcInvalidTokenException('Token signature could not be validated using the provided secret');
default:
throw new OidcInvalidTokenException("Only $validSignatures signatures validation are supported. Token reports using {$this->header['alg']}");
}
} }
/** /**

View File

@@ -74,7 +74,7 @@ class OidcProviderSettings
{ {
$this->validateInitial(); $this->validateInitial();
$required = ['tokenEndpoint', 'authorizationEndpoint']; $required = ['keys', 'tokenEndpoint', 'authorizationEndpoint'];
foreach ($required as $prop) { foreach ($required as $prop) {
if (empty($this->$prop)) { if (empty($this->$prop)) {
throw new InvalidArgumentException("Missing required configuration \"{$prop}\" value"); throw new InvalidArgumentException("Missing required configuration \"{$prop}\" value");

View File

@@ -14,6 +14,7 @@ use BookStack\Theming\ThemeEvents;
use BookStack\Uploads\UserAvatars; use BookStack\Uploads\UserAvatars;
use BookStack\Users\Models\User; use BookStack\Users\Models\User;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException; use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
/** /**
@@ -139,7 +140,7 @@ class OidcService
'redirectUri' => url('/oidc/callback'), 'redirectUri' => url('/oidc/callback'),
], [ ], [
'httpClient' => $this->http->buildClient(5), 'httpClient' => $this->http->buildClient(5),
'optionProvider' => new OidcHttpBasicWithClientIdOptionProvider(), 'optionProvider' => new HttpBasicAuthOptionProvider(),
]); ]);
foreach ($this->getAdditionalScopes() as $scope) { foreach ($this->getAdditionalScopes() as $scope) {
@@ -198,7 +199,7 @@ class OidcService
} }
try { try {
$idToken->validate($settings); $idToken->validate($settings->clientId);
} catch (OidcInvalidTokenException $exception) { } catch (OidcInvalidTokenException $exception) {
throw new OidcException("ID token validation failed with error: {$exception->getMessage()}"); throw new OidcException("ID token validation failed with error: {$exception->getMessage()}");
} }

View File

@@ -10,7 +10,7 @@ class PwaManifestBuilder
// does not start a session, so we won't have current user context. // does not start a session, so we won't have current user context.
// This was attempted but removed since manifest calls could affect user session // This was attempted but removed since manifest calls could affect user session
// history tracking and back redirection. // history tracking and back redirection.
// Context: https://github.com/BookStackApp/BookStack/issues/4649 // Context: https://codeberg.org/bookstack/bookstack/issues/4649
$darkMode = (bool) setting()->getForCurrentUser('dark-mode-enabled'); $darkMode = (bool) setting()->getForCurrentUser('dark-mode-enabled');
$appName = setting('app-name'); $appName = setting('app-name');

View File

@@ -68,7 +68,7 @@ return [
* Times-Roman, Times-Bold, Times-BoldItalic, Times-Italic, * Times-Roman, Times-Bold, Times-BoldItalic, Times-Italic,
* Symbol, ZapfDingbats. * Symbol, ZapfDingbats.
*/ */
'font_dir' => storage_path('fonts/'), // advised by dompdf (https://github.com/dompdf/dompdf/pull/782) 'font_dir' => storage_path('fonts/dompdf'), // advised by dompdf (https://github.com/dompdf/dompdf/pull/782)
/** /**
* The location of the DOMPDF font cache directory. * The location of the DOMPDF font cache directory.
@@ -78,7 +78,7 @@ return [
* *
* Note: This directory must exist and be writable by the webserver process. * Note: This directory must exist and be writable by the webserver process.
*/ */
'font_cache' => storage_path('fonts/'), 'font_cache' => storage_path('fonts/dompdf/cache'),
/** /**
* The location of a temporary directory. * The location of a temporary directory.

View File

@@ -4,6 +4,8 @@ namespace BookStack\Exports;
use BookStack\Exceptions\PdfExportException; use BookStack\Exceptions\PdfExportException;
use Dompdf\Dompdf; use Dompdf\Dompdf;
use FontLib\Font;
use Illuminate\Support\Str;
use Knp\Snappy\Pdf as SnappyPdf; use Knp\Snappy\Pdf as SnappyPdf;
use Symfony\Component\Process\Exception\ProcessTimedOutException; use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Process; use Symfony\Component\Process\Process;
@@ -60,12 +62,65 @@ class PdfGenerator
$domPdf = new Dompdf($options); $domPdf = new Dompdf($options);
$domPdf->setBasePath(base_path('public')); $domPdf->setBasePath(base_path('public'));
$fontMetrics = $domPdf->getFontMetrics();
$userFontfamilies = $this->getUserDomPdfFontFamilies();
foreach ($userFontfamilies as $fontFamily => $fonts) {
try {
$fontMetrics->setFontFamily($fontFamily, $fonts);
} catch (\Exception $exception) {
$expectedPath = storage_path('fonts/dompdf');
throw new PdfExportException("Failed to create required font data in {$expectedPath}, Ensure all content in this location is writable by the web server");
}
}
$domPdf->loadHTML($this->convertEntities($html)); $domPdf->loadHTML($this->convertEntities($html));
$domPdf->render(); $domPdf->render();
return (string) $domPdf->output(); return (string) $domPdf->output();
} }
/**
* @return array<string, array<string, string>>
*/
protected function getUserDomPdfFontFamilies(): array
{
$fontStore = storage_path('fonts/dompdf');
if (!is_dir($fontStore)) {
return [];
}
$fontFamilies = [];
$fontFiles = glob($fontStore . DIRECTORY_SEPARATOR . '*.ttf');
foreach ($fontFiles as $fontFile) {
$fontFileName = basename($fontFile, '.ttf');
$expectedUfm = $fontStore . DIRECTORY_SEPARATOR . $fontFileName . '.ufm';
if (!file_exists($expectedUfm)) {
$font = Font::load($fontFile);
$font->parse();
try {
$font->saveAdobeFontMetrics($expectedUfm);
} catch (\Exception $exception) {
throw new PdfExportException("Failed to create required font data at $expectedUfm, Ensure this location is writable by the web server");
}
}
$nameParts = explode('-', $fontFileName);
if (count($nameParts) === 1 || $nameParts[1] === 'Regular') {
$nameParts[1] = 'Normal';
}
$family = trim(strtolower(preg_replace('/([A-Z])/', ' $1', $nameParts[0])));
$variation = Str::snake($nameParts[1]);
if (!isset($fontFamilies[$family])) {
$fontFamilies[$family] = [];
}
$fontFamilies[$family][$variation] = $fontStore . DIRECTORY_SEPARATOR . $fontFileName;
}
return $fontFamilies;
}
/** /**
* @throws PdfExportException * @throws PdfExportException
*/ */

View File

@@ -1,10 +1,13 @@
project_id: "377219" project_id: "377219"
project_identifier: bookstack project_identifier: bookstack
api_token_env: CROWDIN_PERSONAL_TOKEN
base_path: . base_path: .
preserve_hierarchy: false preserve_hierarchy: false
pull_request_title: Updated translations with latest Crowdin changes pull_request_title: Updated translations with latest Crowdin changes
pull_request_labels: pull_request_labels:
- ":earth_africa: Translations" - "Translations"
files: files:
- source: /lang/en/*.php - source: /lang/en/*.php
translation: /lang/%two_letters_code%/%original_file_name% translation: /lang/%two_letters_code%/%original_file_name%

View File

@@ -18,7 +18,7 @@ ARG BRANCH=development
# Download BookStack & install PHP deps # Download BookStack & install PHP deps
RUN mkdir -p /var/www && \ RUN mkdir -p /var/www && \
git clone https://github.com/bookstackapp/bookstack.git --branch "$BRANCH" --single-branch /var/www/bookstack && \ git clone https://codeberg.org/bookstack/bookstack.git --branch "$BRANCH" --single-branch /var/www/bookstack && \
cd /var/www/bookstack && \ cd /var/www/bookstack && \
wget https://raw.githubusercontent.com/composer/getcomposer.org/f3108f64b4e1c1ce6eb462b159956461592b3e3e/web/installer -O - -q | php -- --quiet --filename=composer && \ wget https://raw.githubusercontent.com/composer/getcomposer.org/f3108f64b4e1c1ce6eb462b159956461592b3e3e/web/installer -O - -q | php -- --quiet --filename=composer && \
php composer install php composer install

View File

@@ -74,7 +74,7 @@ Theme::registerCommand(new SayHelloCommand());
## Available Events ## Available Events
All available events dispatched by BookStack are exposed as static properties on the `\BookStack\Theming\ThemeEvents` class, which can be found within the file `app/Theming/ThemeEvents.php` relative to your root BookStack folder. Alternatively, the events for the latest release can be [seen on GitHub here](https://github.com/BookStackApp/BookStack/blob/release/app/Theming/ThemeEvents.php). All available events dispatched by BookStack are exposed as static properties on the `\BookStack\Theming\ThemeEvents` class, which can be found within the file `app/Theming/ThemeEvents.php` relative to your root BookStack folder. Alternatively, the events for the latest release can be [seen on Codeberg here](https://codeberg.org/bookstack/bookstack/src/branch/release/app/Theming/ThemeEvents.php).
The comments above each constant with the `ThemeEvents.php` file describe the dispatch conditions of the event, in addition to the arguments the action will receive. The comments may also describe any ways the return value of the action may be used. The comments above each constant with the `ThemeEvents.php` file describe the dispatch conditions of the event, in addition to the arguments the action will receive. The comments may also describe any ways the return value of the action may be used.

View File

@@ -12,13 +12,13 @@ Feature releases are generally larger, bringing new features in addition to fixe
### Release Planning Process ### Release Planning Process
Each BookStack release will have a [milestone](https://github.com/BookStackApp/BookStack/milestones) created with issues & pull requests assigned to it to define what will be in that release. Milestones are built up then worked through until complete at which point, after some testing and documentation updates, the release will be deployed. Each BookStack release will have a [milestone](https://codeberg.org/bookstack/bookstack/milestones) created with issues & pull requests assigned to it to define what will be in that release. Milestones are built up then worked through until complete at which point, after some testing and documentation updates, the release will be deployed.
### Release Announcements ### Release Announcements
Feature releases, and some patch releases, will be accompanied by a post on the [BookStack blog](https://www.bookstackapp.com/blog/) which will provide additional detail on features, changes & updates otherwise the [GitHub release page](https://github.com/BookStackApp/BookStack/releases) will show a list of changes. You can sign up to be alerted to new BookStack blog posts (once per week maximum) [at this link](https://updates.bookstackapp.com/signup/bookstack-news-and-updates). Feature releases, and some patch releases, will be accompanied by a post on the [BookStack blog](https://www.bookstackapp.com/blog/) which will provide additional detail on features, changes & updates otherwise the [Codeberg release page](https://codeberg.org/bookstack/bookstack/releases) will show a list of changes. You can sign up to be alerted to new BookStack blog posts (once per week maximum) [at this link](https://updates.bookstackapp.com/signup/bookstack-news-and-updates).
### Release Technical Process ### Release Technical Process
Deploying a release, at a high level, simply involves merging the development branch into the release branch before then building & committing any release-only assets. Deploying a release, at a high level, simply involves merging the development branch into the release branch before then building & committing any release-only assets.
A helper script [can be found in our](https://github.com/BookStackApp/devops/blob/main/meta-scripts/bookstack-release-steps) devops repo which provides the steps and commands for deploying a new release. A helper script [can be found in our](https://codeberg.org/bookstack/devops/src/branch/main/meta-scripts/bookstack-release-steps) devops repo which provides the steps and commands for deploying a new release.

View File

@@ -2,7 +2,7 @@
**Warning: This API is currently in development and may change without notice.** **Warning: This API is currently in development and may change without notice.**
Feedback is very much welcomed via this issue: https://github.com/BookStackApp/BookStack/issues/5937 Feedback is very much welcomed via this issue: https://codeberg.org/bookstack/bookstack/issues/5937
This document covers the JavaScript API for the (newer Lexical-based) WYSIWYG editor. This document covers the JavaScript API for the (newer Lexical-based) WYSIWYG editor.
This API is built and designed to abstract the internals of the editor away This API is built and designed to abstract the internals of the editor away

View File

@@ -1,17 +1,15 @@
# BookStack # BookStack
[![GitHub release](https://img.shields.io/github/release/BookStackApp/BookStack.svg)](https://github.com/BookStackApp/BookStack/releases/latest) [![Codeberg release](https://img.shields.io/gitea/v/release/bookstack/bookstack.svg?gitea_url=https://codeberg.org)](https://codeberg.org/bookstack/bookstack/releases/latest)
[![license](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/BookStackApp/BookStack/blob/development/LICENSE) [![license](https://img.shields.io/badge/License-MIT-yellow.svg)](https://codeberg.org/bookstack/bookstack/src/branch/development/LICENSE)
[![Crowdin](https://badges.crowdin.net/bookstack/localized.svg)](https://crowdin.com/project/bookstack) [![Crowdin](https://badges.crowdin.net/bookstack/localized.svg)](https://crowdin.com/project/bookstack)
[![Build Status](https://github.com/BookStackApp/BookStack/workflows/test-php/badge.svg)](https://github.com/BookStackApp/BookStack/actions) [![Build Status](https://codeberg.org/bookstack/bookstack/badges/workflows/test-php.yml/badge.svg)](https://codeberg.org/bookstack/bookstack/actions?workflow=test-php.yml)
[![Lint Status](https://github.com/BookStackApp/BookStack/workflows/lint-php/badge.svg)](https://github.com/BookStackApp/BookStack/actions) [![Lint Status](https://codeberg.org/bookstack/bookstack/badges/workflows/lint-php.yml/badge.svg)](https://codeberg.org/bookstack/bookstack/actions?workflow=lint-php.yml)
[![php-metrics](https://img.shields.io/static/v1?label=Metrics&message=php&color=4F5B93)](https://source.bookstackapp.com/php-stats/index.html) [![php-metrics](https://img.shields.io/static/v1?label=Metrics&message=php&color=4F5B93)](https://source.bookstackapp.com/php-stats/index.html)
<br> <br>
[![Alternate Source](https://img.shields.io/static/v1?label=Alt+Source&message=Git&color=ef391a&logo=git)](https://source.bookstackapp.com/) [![Alternate Source](https://img.shields.io/static/v1?label=Alt+Source&message=Git&color=ef391a&logo=git)](https://source.bookstackapp.com/)
[![Repo Stats](https://img.shields.io/static/v1?label=GitHub+project&message=stats&color=f27e3f)](https://gh-stats.bookstackapp.com/)
[![Community Discussions](https://img.shields.io/static/v1?label=Community&message=Discussions&color=4d36c4&logo=zulip)](https://community.bookstackapp.com/) [![Community Discussions](https://img.shields.io/static/v1?label=Community&message=Discussions&color=4d36c4&logo=zulip)](https://community.bookstackapp.com/)
[![Mastodon](https://img.shields.io/static/v1?label=Mastodon&message=@bookstack&color=595aff&logo=mastodon)](https://www.bookstackapp.com/links/mastodon) [![Mastodon](https://img.shields.io/static/v1?label=Mastodon&message=@bookstack&color=595aff&logo=mastodon)](https://www.bookstackapp.com/links/mastodon)
[![Discord](https://img.shields.io/static/v1?label=Discord&message=chat&color=738adb&logo=discord)](https://www.bookstackapp.com/links/discord)
<br> <br>
[![PeerTube](https://img.shields.io/static/v1?label=PeerTube&message=bookstack@foss.video&color=f2690d&logo=peertube)](https://foss.video/c/bookstack) [![PeerTube](https://img.shields.io/static/v1?label=PeerTube&message=bookstack@foss.video&color=f2690d&logo=peertube)](https://foss.video/c/bookstack)
[![YouTube](https://img.shields.io/static/v1?label=YouTube&message=bookstackapp&color=ff0000&logo=youtube)](https://www.youtube.com/bookstackapp) [![YouTube](https://img.shields.io/static/v1?label=YouTube&message=bookstackapp&color=ff0000&logo=youtube)](https://www.youtube.com/bookstackapp)
@@ -23,7 +21,7 @@ A platform for storing and organising information and documentation. Details for
* [Demo Instance](https://demo.bookstackapp.com) * [Demo Instance](https://demo.bookstackapp.com)
* [Screenshots](https://www.bookstackapp.com/#screenshots) * [Screenshots](https://www.bookstackapp.com/#screenshots)
* [BookStack Blog](https://www.bookstackapp.com/blog) * [BookStack Blog](https://www.bookstackapp.com/blog)
* [Issue List](https://github.com/BookStackApp/BookStack/issues) * [Issue List](https://codeberg.org/bookstack/bookstack/issues)
* [Community Discussions](https://community.bookstackapp.com/) * [Community Discussions](https://community.bookstackapp.com/)
* [Support Options](https://www.bookstackapp.com/support/) * [Support Options](https://www.bookstackapp.com/support/)
@@ -72,7 +70,7 @@ Big thanks to these companies for supporting the project.
<td align="center"><a href="https://www.stellarhosted.com/bookstack/" target="_blank"> <td align="center"><a href="https://www.stellarhosted.com/bookstack/" target="_blank">
<img width="240" src="https://www.bookstackapp.com/images/sponsors/stellarhosted.png" alt="Stellar Hosted"> <img width="240" src="https://www.bookstackapp.com/images/sponsors/stellarhosted.png" alt="Stellar Hosted">
</a></td> </a></td>
<td align="center" style="text-align: center"><a href="https://nws.netways.de/apps/bookstack/" target="_blank"> <td align="center" style="text-align: center"><a href="https://nws.netways.de" target="_blank">
<img width="240" src="https://www.bookstackapp.com/images/sponsors/netways.png" alt="NETWAYS Web Services"> <img width="240" src="https://www.bookstackapp.com/images/sponsors/netways.png" alt="NETWAYS Web Services">
</a></td> </a></td>
</tr> </tr>
@@ -113,13 +111,13 @@ Translations for text within BookStack are managed through the [BookStack projec
Please use [Crowdin](https://crowdin.com/project/bookstack) to contribute translations instead of opening a pull request. The translations within the working codebase can be out-of-date, and merging via code can cause conflicts & sync issues. If for some reason you can't use Crowdin feel free to open an issue to discuss alternative options. Please use [Crowdin](https://crowdin.com/project/bookstack) to contribute translations instead of opening a pull request. The translations within the working codebase can be out-of-date, and merging via code can cause conflicts & sync issues. If for some reason you can't use Crowdin feel free to open an issue to discuss alternative options.
If you'd like a new language to be added to Crowdin, for you to be able to provide translations for, please [open a new issue here](https://github.com/BookStackApp/BookStack/issues/new?template=language_request.yml). If you'd like a new language to be added to Crowdin, for you to be able to provide translations for, please [open a new issue here](https://codeberg.org/bookstack/bookstack/issues/new?template=.forgejo%2fISSUE_TEMPLATE%2flanguage_request.yml).
Please note, translations in BookStack are provided to the "Crowdin Global Translation Memory" which helps BookStack and other projects with finding translations. If you are not happy with contributing to this then providing translations to BookStack, even manually via GitHub, is not advised. Please note, translations in BookStack are provided to the "Crowdin Global Translation Memory" which helps BookStack and other projects with finding translations. If you are not happy with contributing to this then providing translations to BookStack, even manually via code, is not advised.
## 🎁 Contributing, Issues & Pull Requests ## 🎁 Contributing, Issues & Pull Requests
Feel free to [create issues](https://github.com/BookStackApp/BookStack/issues/new/choose) to request new features or to report bugs & problems. Just please follow the template given when creating the issue. Feel free to [create issues](https://codeberg.org/bookstack/bookstack/issues/new/choose) to request new features or to report bugs & problems. Just please follow the template given when creating the issue.
Pull requests are welcome but, unless it's a small tweak, it may be best to open the pull request early or create an issue for your intended change to discuss how it will fit into the project and plan out the merge. Just because a feature request exists, or is tagged, does not mean that feature would be accepted into the core project. Pull requests are welcome but, unless it's a small tweak, it may be best to open the pull request early or create an issue for your intended change to discuss how it will fit into the project and plan out the merge. Just because a feature request exists, or is tagged, does not mean that feature would be accepted into the core project.
@@ -134,7 +132,7 @@ Security information for administering a BookStack instance can be found on the
If you'd like to be notified of new potential security concerns you can [sign-up to the BookStack security mailing list](https://updates.bookstackapp.com/signup/bookstack-security-updates). If you'd like to be notified of new potential security concerns you can [sign-up to the BookStack security mailing list](https://updates.bookstackapp.com/signup/bookstack-security-updates).
If you would like to report a security concern, details of doing so [can be found here](https://github.com/BookStackApp/BookStack/blob/development/.github/SECURITY.md). If you would like to report a security concern, details of doing so [can be found here](.forgejo/SECURITY.md).
## ♿ Accessibility ## ♿ Accessibility
@@ -142,18 +140,18 @@ We want BookStack to remain accessible to as many people as possible. We aim for
## 🖥️ Website, Docs & Blog ## 🖥️ Website, Docs & Blog
The website which contains the project docs & blog can be found in the [BookStackApp/website](https://codeberg.org/bookstack/website) repo. The website which contains the project docs & blog can be found in the [bookstack/website](https://codeberg.org/bookstack/website) repo.
## ⚖️ License ## ⚖️ License
The BookStack source is provided under the [MIT License](https://github.com/BookStackApp/BookStack/blob/development/LICENSE). The BookStack source is provided under the [MIT License](https://codeberg.org/bookstack/bookstack/src/branch/development/LICENSE).
The libraries used by, and included with, BookStack are provided under their own licenses and copyright. The libraries used by, and included with, BookStack are provided under their own licenses and copyright.
The licenses for many of our core dependencies can be found in the attribution list below, but this is not an exhaustive list of all projects used within BookStack. The licenses for many of our core dependencies can be found in the attribution list below, but this is not an exhaustive list of all projects used within BookStack.
## 👪 Attribution ## 👪 Attribution
The great people that have worked to build and improve BookStack can [be seen here](https://github.com/BookStackApp/BookStack/graphs/contributors). The wonderful people that have provided translations, either through GitHub or via Crowdin [can be seen here](https://github.com/BookStackApp/BookStack/blob/development/.github/translators.txt). The great people that have worked to build and improve BookStack can [be seen here](https://codeberg.org/bookstack/bookstack/activity/contributors). The wonderful people that have provided translations, either through GitHub, Codeberg or via Crowdin [can be seen here](https://codeberg.org/bookstack/bookstack/src/branch/development/.github/translators.txt).
Below are the great open-source projects used to help build BookStack. Below are the great open-source projects used to help build BookStack.
Note: This is not an exhaustive list of all libraries and projects that would be used in an active BookStack instance. Note: This is not an exhaustive list of all libraries and projects that would be used in an active BookStack instance.

View File

@@ -14,11 +14,11 @@
HTTP POST calls upon events occurring in BookStack. HTTP POST calls upon events occurring in BookStack.
</li> </li>
<li> <li>
<a href="https://github.com/BookStackApp/BookStack/blob/development/dev/docs/visual-theme-system.md" target="_blank" rel="noopener noreferrer">Visual Theme System</a> - <a href="https://codeberg.org/bookstack/bookstack/src/branch/development/dev/docs/visual-theme-system.md" target="_blank" rel="noopener noreferrer">Visual Theme System</a> -
Methods to override views, translations and icons within BookStack. Methods to override views, translations and icons within BookStack.
</li> </li>
<li> <li>
<a href="https://github.com/BookStackApp/BookStack/blob/development/dev/docs/logical-theme-system.md" target="_blank" rel="noopener noreferrer">Logical Theme System</a> - <a href="https://codeberg.org/bookstack/bookstack/src/branch/development/dev/docs/logical-theme-system.md" target="_blank" rel="noopener noreferrer">Logical Theme System</a> -
Methods to extend back-end functionality within BookStack. Methods to extend back-end functionality within BookStack.
</li> </li>
</ul> </ul>

View File

@@ -113,13 +113,13 @@
<a href="https://www.bookstackapp.com/docs/admin/debugging/" target="_blank">Review BookStack debugging documentation &raquo;</a> <a href="https://www.bookstackapp.com/docs/admin/debugging/" target="_blank">Review BookStack debugging documentation &raquo;</a>
</li> </li>
<li> <li>
<a href="https://github.com/BookStackApp/BookStack/releases" target="_blank">Ensure your instance is up-to-date &raquo;</a> <a href="https://codeberg.org/bookstack/bookstack/releases" target="_blank">Ensure your instance is up-to-date &raquo;</a>
</li> </li>
<li> <li>
<a href="https://github.com/BookStackApp/BookStack/issues?q=is%3Aissue+{{ urlencode($error) }}" target="_blank">Search for the issue on GitHub &raquo;</a> <a href="https://codeberg.org/bookstack/bookstack/issues?q={{ urlencode($error) }}" target="_blank">Search for the issue on GitHub &raquo;</a>
</li> </li>
<li> <li>
<a href="https://discord.gg/ztkBqR2" target="_blank">Ask for help via Discord &raquo;</a> <a href="https://community.bookstackapp.com" target="_blank">Ask for help in our community forums &raquo;</a>
</li> </li>
<li> <li>
<a href="https://duckduckgo.com/?q={{urlencode("BookStack {$error}")}}" target="_blank">Search the error message &raquo;</a> <a href="https://duckduckgo.com/?q={{urlencode("BookStack {$error}")}}" target="_blank">Search the error message &raquo;</a>

View File

@@ -18,7 +18,7 @@
<h5 class="mt-xl">{{ trans('settings.system_version') }}</h5> <h5 class="mt-xl">{{ trans('settings.system_version') }}</h5>
<div class="py-xs"> <div class="py-xs">
<a target="_blank" rel="noopener noreferrer" href="https://github.com/BookStackApp/BookStack/releases"> <a target="_blank" rel="noopener noreferrer" href="https://codeberg.org/bookstack/bookstack/releases">
BookStack @if(!str_starts_with($version, 'v')) version @endif {{ $version }} BookStack @if(!str_starts_with($version, 'v')) version @endif {{ $version }}
</a> </a>
<br> <br>

View File

@@ -1,2 +1,6 @@
# Font cache files have once been stored directly in this folder
# therefore its important the contents non-ignored by git
# are chosen selectively
* *
!.gitignore !.gitignore
!dompdf/

3
storage/fonts/dompdf/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
*
!.gitignore
!cache/

2
storage/fonts/dompdf/cache/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*
!.gitignore

View File

@@ -27,7 +27,7 @@ class DebugViewTest extends TestCase
$resp->assertSeeText('BookStack Version: ' . trim(file_get_contents(base_path('version')))); $resp->assertSeeText('BookStack Version: ' . trim(file_get_contents(base_path('version'))));
// Dynamic help links // Dynamic help links
$this->withHtml($resp)->assertElementExists('a[href*="q=' . urlencode('BookStack An error occurred during testing') . '"]'); $this->withHtml($resp)->assertElementExists('a[href*="q=' . urlencode('BookStack An error occurred during testing') . '"]');
$this->withHtml($resp)->assertElementExists('a[href*="?q=is%3Aissue+' . urlencode('An error occurred during testing') . '"]'); $this->withHtml($resp)->assertElementExists('a[href*="?q=' . urlencode('An error occurred during testing') . '"]');
} }
public function test_debug_view_only_shows_when_debug_mode_is_enabled() public function test_debug_view_only_shows_when_debug_mode_is_enabled()

View File

@@ -370,7 +370,7 @@ class PageContentTest extends TestCase
public function test_base64_images_within_html_blanked_if_not_supported_extension_for_extract() public function test_base64_images_within_html_blanked_if_not_supported_extension_for_extract()
{ {
// Relevant to https://github.com/BookStackApp/BookStack/issues/3010 and other cases // Relevant to https://codeberg.org/bookstack/bookstack/issues/3010 and other cases
$extensions = [ $extensions = [
'jiff', 'pngr', 'png ', ' png', '.png', 'png.', 'p.ng', ',png', 'jiff', 'pngr', 'png ', ' png', '.png', 'png.', 'p.ng', ',png',
'data:image/png', ',data:image/png', 'data:image/png', ',data:image/png',

View File

@@ -79,6 +79,39 @@ class PdfExportTest extends TestCase
$this->assertStringContainsString('<details open="open"', $pdfHtml); $this->assertStringContainsString('<details open="open"', $pdfHtml);
} }
public function test_custom_fonts_loaded_for_dom_pdf_when_used()
{
// Set up custom font usage
$page = $this->entities->page()->forceFill([
'html' => '<p><strong>Bold</strong>text</p>',
]);
$page->save();
$this->setSettings([
'app-custom-head' => '<style>* { font-family: "meow words"}</style>'
]);
$normalFont = $this->files->testFilePath('fonts/Cardiff.ttf');
$normalFontTarget = storage_path('fonts/dompdf/MeowWords.ttf');
$boldFont = $this->files->testFilePath('fonts/Cardiff-Bold.ttf');
$boldFontTarget = storage_path('fonts/dompdf/MeowWords-Bold.ttf');
copy($normalFont, $normalFontTarget);
copy($boldFont, $boldFontTarget);
$resp = $this->asEditor()->get($page->getUrl('/export/pdf'));
$resp->assertStatus(200);
// Existance of UFM files indicates the metrics have been generated
$this->assertFileExists(storage_path('fonts/dompdf/MeowWords.ufm'));
$this->assertFileExists(storage_path('fonts/dompdf/MeowWords-Bold.ufm'));
// Existence of cache json files indicates the fonts have been used
$this->assertFileExists(storage_path('fonts/dompdf/cache/MeowWords.ufm.json'));
$this->assertFileExists(storage_path('fonts/dompdf/cache/MeowWords-Bold.ufm.json'));
$filesToCleanUp = [...glob(storage_path('fonts/dompdf/Meow*')), ...glob(storage_path('fonts/dompdf/cache/Meow*'))];
foreach ($filesToCleanUp as $file) {
unlink($file);
}
}
public function test_wkhtmltopdf_only_used_when_allow_untrusted_is_true() public function test_wkhtmltopdf_only_used_when_allow_untrusted_is_true()
{ {
$page = $this->entities->page(); $page = $this->entities->page();

View File

@@ -2,7 +2,6 @@
namespace Tests\Helpers; namespace Tests\Helpers;
use BookStack\Access\Oidc\OidcProviderSettings;
use phpseclib3\Crypt\RSA; use phpseclib3\Crypt\RSA;
/** /**
@@ -120,41 +119,6 @@ q/1PY4iJviGKddtmfClH3v4=
-----END PRIVATE KEY-----'; -----END PRIVATE KEY-----';
} }
public static function defaultSecret(): string
{
return 'test-client-secret-for-hs256';
}
/**
* Build a minimal OidcProviderSettings for use in token validation tests.
*/
public static function defaultProviderSettings(array $overrides = []): OidcProviderSettings
{
return new OidcProviderSettings(array_merge([
'issuer' => static::defaultIssuer(),
'clientId' => static::defaultClientId(),
'clientSecret' => static::defaultSecret(),
], $overrides));
}
/**
* Build an HS256-signed ID token using the given secret.
*/
public static function hs256IdToken(string $secret, array $payloadOverrides = []): string
{
$payload = array_merge(static::defaultPayload(), $payloadOverrides);
$header = ['alg' => 'HS256', 'typ' => 'JWT'];
$top = implode('.', [
static::base64UrlEncode(json_encode($header)),
static::base64UrlEncode(json_encode($payload)),
]);
$signature = hash_hmac('sha256', $top, $secret, true);
return $top . '.' . static::base64UrlEncode($signature);
}
public static function publicJwkKeyArray(): array public static function publicJwkKeyArray(): array
{ {
return [ return [

View File

@@ -15,7 +15,7 @@ class OidcIdTokenTest extends TestCase
OidcJwtHelper::publicJwkKeyArray(), OidcJwtHelper::publicJwkKeyArray(),
]); ]);
$this->assertTrue($token->validate(OidcJwtHelper::defaultProviderSettings())); $this->assertTrue($token->validate('xxyyzz.aaa.bbccdd.123'));
} }
public function test_get_claim_returns_value_if_existing() public function test_get_claim_returns_value_if_existing()
@@ -56,7 +56,7 @@ class OidcIdTokenTest extends TestCase
$err = null; $err = null;
try { try {
$token->validate(OidcJwtHelper::defaultProviderSettings()); $token->validate('abc');
} catch (\Exception $exception) { } catch (\Exception $exception) {
$err = $exception; $err = $exception;
} }
@@ -71,7 +71,7 @@ class OidcIdTokenTest extends TestCase
$token = new OidcIdToken(OidcJwtHelper::idToken(), OidcJwtHelper::defaultIssuer(), []); $token = new OidcIdToken(OidcJwtHelper::idToken(), OidcJwtHelper::defaultIssuer(), []);
$this->expectException(OidcInvalidTokenException::class); $this->expectException(OidcInvalidTokenException::class);
$this->expectExceptionMessage('Token signature could not be validated using the provided keys'); $this->expectExceptionMessage('Token signature could not be validated using the provided keys');
$token->validate(OidcJwtHelper::defaultProviderSettings()); $token->validate('abc');
} }
public function test_error_thrown_if_token_signature_not_validated_from_non_matching_key() public function test_error_thrown_if_token_signature_not_validated_from_non_matching_key()
@@ -83,7 +83,7 @@ class OidcIdTokenTest extends TestCase
]); ]);
$this->expectException(OidcInvalidTokenException::class); $this->expectException(OidcInvalidTokenException::class);
$this->expectExceptionMessage('Token signature could not be validated using the provided keys'); $this->expectExceptionMessage('Token signature could not be validated using the provided keys');
$token->validate(OidcJwtHelper::defaultProviderSettings()); $token->validate('abc');
} }
public function test_error_thrown_if_invalid_key_provided() public function test_error_thrown_if_invalid_key_provided()
@@ -91,34 +91,15 @@ class OidcIdTokenTest extends TestCase
$token = new OidcIdToken(OidcJwtHelper::idToken(), OidcJwtHelper::defaultIssuer(), ['url://example.com']); $token = new OidcIdToken(OidcJwtHelper::idToken(), OidcJwtHelper::defaultIssuer(), ['url://example.com']);
$this->expectException(OidcInvalidTokenException::class); $this->expectException(OidcInvalidTokenException::class);
$this->expectExceptionMessage('Unexpected type of key value provided'); $this->expectExceptionMessage('Unexpected type of key value provided');
$token->validate(OidcJwtHelper::defaultProviderSettings()); $token->validate('abc');
} }
public function test_error_thrown_if_token_algorithm_is_not_supported() public function test_error_thrown_if_token_algorithm_is_not_rs256()
{ {
$token = new OidcIdToken(OidcJwtHelper::idToken([], ['alg' => 'ES256']), OidcJwtHelper::defaultIssuer(), []); $token = new OidcIdToken(OidcJwtHelper::idToken([], ['alg' => 'HS256']), OidcJwtHelper::defaultIssuer(), []);
$this->expectException(OidcInvalidTokenException::class); $this->expectException(OidcInvalidTokenException::class);
$this->expectExceptionMessage('Only HS256, RS256 signatures validation are supported. Token reports using ES256'); $this->expectExceptionMessage('Only RS256 signature validation is supported. Token reports using HS256');
$token->validate(OidcJwtHelper::defaultProviderSettings()); $token->validate('abc');
}
public function test_hs256_token_passes_validation_with_correct_secret()
{
$secret = OidcJwtHelper::defaultSecret();
$token = new OidcIdToken(OidcJwtHelper::hs256IdToken($secret), OidcJwtHelper::defaultIssuer(), []);
$settings = OidcJwtHelper::defaultProviderSettings(['clientSecret' => $secret]);
$this->assertTrue($token->validate($settings));
}
public function test_hs256_token_fails_validation_with_wrong_secret()
{
$token = new OidcIdToken(OidcJwtHelper::hs256IdToken('correct-secret'), OidcJwtHelper::defaultIssuer(), []);
$settings = OidcJwtHelper::defaultProviderSettings(['clientSecret' => 'wrong-secret']);
$this->expectException(OidcInvalidTokenException::class);
$this->expectExceptionMessage('Token signature could not be validated using the provided secret');
$token->validate($settings);
} }
public function test_token_claim_error_cases() public function test_token_claim_error_cases()
@@ -160,7 +141,7 @@ class OidcIdTokenTest extends TestCase
$err = null; $err = null;
try { try {
$token->validate(OidcJwtHelper::defaultProviderSettings()); $token->validate('xxyyzz.aaa.bbccdd.123');
} catch (\Exception $exception) { } catch (\Exception $exception) {
$err = $exception; $err = $exception;
} }
@@ -179,7 +160,7 @@ class OidcIdTokenTest extends TestCase
$testFilePath, $testFilePath,
]); ]);
$this->assertTrue($token->validate(OidcJwtHelper::defaultProviderSettings())); $this->assertTrue($token->validate('xxyyzz.aaa.bbccdd.123'));
unlink($testFilePath); unlink($testFilePath);
} }
} }

View File

@@ -75,7 +75,7 @@ class ImageTest extends TestCase
public function test_image_display_thumbnail_generation_for_animated_avif_images_uses_original_file() public function test_image_display_thumbnail_generation_for_animated_avif_images_uses_original_file()
{ {
if (! function_exists('imageavif')) { if ((gd_info()['AVIF Support'] ?? false) !== true) {
$this->markTestSkipped('imageavif() is not available'); $this->markTestSkipped('imageavif() is not available');
} }

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,2 @@
Font files by Roger White, in public domain.
https://web.archive.org/web/20110609213636/http://www.rogersfonts.org.uk/

View File

@@ -1 +1 @@
v26.01-dev v26.05-dev