Skip to content

Commit 3424351

Browse files
committed
Merge branch 'development' into release
2 parents 606f9d9 + a3a776d commit 3424351

File tree

439 files changed

+7703
-3737
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

439 files changed

+7703
-3737
lines changed

.env.example.complete

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,11 @@ LDAP_SERVER=false
215215
LDAP_BASE_DN=false
216216
LDAP_DN=false
217217
LDAP_PASS=false
218-
LDAP_USER_FILTER=false
218+
LDAP_USER_FILTER="(&(uid={user}))"
219219
LDAP_VERSION=false
220220
LDAP_START_TLS=false
221221
LDAP_TLS_INSECURE=false
222+
LDAP_TLS_CA_CERT=false
222223
LDAP_ID_ATTRIBUTE=uid
223224
LDAP_EMAIL_ATTRIBUTE=mail
224225
LDAP_DISPLAY_NAME_ATTRIBUTE=cn
@@ -267,6 +268,7 @@ OIDC_ISSUER_DISCOVER=false
267268
OIDC_PUBLIC_KEY=null
268269
OIDC_AUTH_ENDPOINT=null
269270
OIDC_TOKEN_ENDPOINT=null
271+
OIDC_USERINFO_ENDPOINT=null
270272
OIDC_ADDITIONAL_SCOPES=null
271273
OIDC_DUMP_USER_DETAILS=false
272274
OIDC_USER_TO_GROUPS=false
@@ -324,6 +326,14 @@ FILE_UPLOAD_SIZE_LIMIT=50
324326
# Can be 'a4' or 'letter'.
325327
EXPORT_PAGE_SIZE=a4
326328

329+
# Export PDF Command
330+
# Set a command which can be used to convert a HTML file into a PDF file.
331+
# When false this will not be used.
332+
# String values represent the command to be called for conversion.
333+
# Supports '{input_html_path}' and '{output_pdf_path}' placeholder values.
334+
# Example: EXPORT_PDF_COMMAND="/scripts/convert.sh {input_html_path} {output_pdf_path}"
335+
EXPORT_PDF_COMMAND=false
336+
327337
# Set path to wkhtmltopdf binary for PDF generation.
328338
# Can be 'false' or a path path like: '/home/bins/wkhtmltopdf'
329339
# When false, BookStack will attempt to find a wkhtmltopdf in the application

.github/translators.txt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ Marc Hagen (MarcHagen) :: Dutch
389389
Kasper Alsøe (zeonos) :: Danish
390390
sultani :: Persian
391391
renge :: Korean
392-
TheGatesDev (thegatesdev) :: Dutch
392+
Tim (thegatesdev) :: Dutch; German Informal; Romanian; French; Catalan; Czech; Danish; German; Finnish; Hungarian; Italian; Japanese; Korean; Polish; Russian; Ukrainian; Chinese Simplified; Chinese Traditional; Portuguese, Brazilian; Persian; Spanish, Argentina; Croatian; Norwegian Nynorsk; Estonian; Uzbek; Norwegian Bokmal
393393
Irdi (irdiOL) :: Albanian
394394
KateBarber :: Welsh
395395
Twister (theuncles75) :: Hebrew
@@ -410,3 +410,15 @@ cracrayol :: French
410410
CapuaSC :: Dutch
411411
Guardian75 :: German Informal
412412
mr-kanister :: German
413+
Michele Bastianelli (makoblaster) :: Italian
414+
jespernissen :: Danish
415+
Andrey (avmaksimov) :: Russian
416+
Gonzalo Loyola (AlFcl) :: Spanish, Argentina; Spanish
417+
grobert63 :: French
418+
wusst. (Supporti) :: German
419+
MaximMaximS :: Czech
420+
damian-klima :: Slovak
421+
crow_ :: Latvian
422+
JocelynDelalande :: French
423+
Jan (JW-CH) :: German Informal
424+
Timo B (lommes) :: German Informal

.github/workflows/analyse-php.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
- name: Setup PHP
1919
uses: shivammathur/setup-php@v2
2020
with:
21-
php-version: 8.1
21+
php-version: 8.3
2222
extensions: gd, mbstring, json, curl, xml, mysql, ldap
2323

2424
- name: Get Composer Cache Directory
@@ -27,10 +27,10 @@ jobs:
2727
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
2828
2929
- name: Cache composer packages
30-
uses: actions/cache@v3
30+
uses: actions/cache@v4
3131
with:
3232
path: ${{ steps.composer-cache.outputs.dir }}
33-
key: ${{ runner.os }}-composer-8.1
33+
key: ${{ runner.os }}-composer-8.3
3434
restore-keys: ${{ runner.os }}-composer-
3535

3636
- name: Install composer dependencies

.github/workflows/test-migrations.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
runs-on: ubuntu-22.04
1717
strategy:
1818
matrix:
19-
php: ['8.0', '8.1', '8.2', '8.3']
19+
php: ['8.1', '8.2', '8.3']
2020
steps:
2121
- uses: actions/checkout@v1
2222

@@ -32,7 +32,7 @@ jobs:
3232
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
3333
3434
- name: Cache composer packages
35-
uses: actions/cache@v3
35+
uses: actions/cache@v4
3636
with:
3737
path: ${{ steps.composer-cache.outputs.dir }}
3838
key: ${{ runner.os }}-composer-${{ matrix.php }}

.github/workflows/test-php.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
runs-on: ubuntu-22.04
1717
strategy:
1818
matrix:
19-
php: ['8.0', '8.1', '8.2', '8.3']
19+
php: ['8.1', '8.2', '8.3']
2020
steps:
2121
- uses: actions/checkout@v1
2222

@@ -32,7 +32,7 @@ jobs:
3232
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
3333
3434
- name: Cache composer packages
35-
uses: actions/cache@v3
35+
uses: actions/cache@v4
3636
with:
3737
path: ${{ steps.composer-cache.outputs.dir }}
3838
key: ${{ runner.os }}-composer-${{ matrix.php }}

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2015-2023, Dan Brown and the BookStack Project contributors.
3+
Copyright (c) 2015-2024, Dan Brown and the BookStack Project contributors.
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

app/Access/Controllers/MfaTotpController.php

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,25 @@ class MfaTotpController extends Controller
1919

2020
protected const SETUP_SECRET_SESSION_KEY = 'mfa-setup-totp-secret';
2121

22+
public function __construct(
23+
protected TotpService $totp
24+
) {
25+
}
26+
2227
/**
2328
* Show a view that generates and displays a TOTP QR code.
2429
*/
25-
public function generate(TotpService $totp)
30+
public function generate()
2631
{
2732
if (session()->has(static::SETUP_SECRET_SESSION_KEY)) {
2833
$totpSecret = decrypt(session()->get(static::SETUP_SECRET_SESSION_KEY));
2934
} else {
30-
$totpSecret = $totp->generateSecret();
35+
$totpSecret = $this->totp->generateSecret();
3136
session()->put(static::SETUP_SECRET_SESSION_KEY, encrypt($totpSecret));
3237
}
3338

34-
$qrCodeUrl = $totp->generateUrl($totpSecret, $this->currentOrLastAttemptedUser());
35-
$svg = $totp->generateQrCodeSvg($qrCodeUrl);
39+
$qrCodeUrl = $this->totp->generateUrl($totpSecret, $this->currentOrLastAttemptedUser());
40+
$svg = $this->totp->generateQrCodeSvg($qrCodeUrl);
3641

3742
$this->setPageTitle(trans('auth.mfa_gen_totp_title'));
3843

@@ -56,7 +61,7 @@ public function confirm(Request $request)
5661
'code' => [
5762
'required',
5863
'max:12', 'min:4',
59-
new TotpValidationRule($totpSecret),
64+
new TotpValidationRule($totpSecret, $this->totp),
6065
],
6166
]);
6267

@@ -87,7 +92,7 @@ public function verify(Request $request, LoginService $loginService, MfaSession
8792
'code' => [
8893
'required',
8994
'max:12', 'min:4',
90-
new TotpValidationRule($totpSecret),
95+
new TotpValidationRule($totpSecret, $this->totp),
9196
],
9297
]);
9398

app/Access/Controllers/RegisterController.php

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,13 @@
1515

1616
class RegisterController extends Controller
1717
{
18-
protected SocialDriverManager $socialDriverManager;
19-
protected RegistrationService $registrationService;
20-
protected LoginService $loginService;
21-
22-
/**
23-
* Create a new controller instance.
24-
*/
2518
public function __construct(
26-
SocialDriverManager $socialDriverManager,
27-
RegistrationService $registrationService,
28-
LoginService $loginService
19+
protected SocialDriverManager $socialDriverManager,
20+
protected RegistrationService $registrationService,
21+
protected LoginService $loginService
2922
) {
3023
$this->middleware('guest');
3124
$this->middleware('guard:standard');
32-
33-
$this->socialDriverManager = $socialDriverManager;
34-
$this->registrationService = $registrationService;
35-
$this->loginService = $loginService;
3625
}
3726

3827
/**
@@ -87,6 +76,8 @@ protected function validator(array $data): ValidatorContract
8776
'name' => ['required', 'min:2', 'max:100'],
8877
'email' => ['required', 'email', 'max:255', 'unique:users'],
8978
'password' => ['required', Password::default()],
79+
// Basic honey for bots that must not be filled in
80+
'username' => ['prohibited'],
9081
]);
9182
}
9283
}

app/Access/LdapService.php

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,12 @@ protected function getConnection()
209209
$this->ldap->setOption(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER);
210210
}
211211

212+
// Configure any user-provided CA cert files for LDAP.
213+
// This option works globally and must be set before a connection is created.
214+
if ($this->config['tls_ca_cert']) {
215+
$this->configureTlsCaCerts($this->config['tls_ca_cert']);
216+
}
217+
212218
$ldapHost = $this->parseServerString($this->config['server']);
213219
$ldapConnection = $this->ldap->connect($ldapHost);
214220

@@ -223,7 +229,14 @@ protected function getConnection()
223229

224230
// Start and verify TLS if it's enabled
225231
if ($this->config['start_tls']) {
226-
$started = $this->ldap->startTls($ldapConnection);
232+
try {
233+
$started = $this->ldap->startTls($ldapConnection);
234+
} catch (\Exception $exception) {
235+
$error = $exception->getMessage() . ' :: ' . ldap_error($ldapConnection);
236+
ldap_get_option($ldapConnection, LDAP_OPT_DIAGNOSTIC_MESSAGE, $detail);
237+
Log::info("LDAP STARTTLS failure: {$error} {$detail}");
238+
throw new LdapException('Could not start TLS connection. Further details in the application log.');
239+
}
227240
if (!$started) {
228241
throw new LdapException('Could not start TLS connection');
229242
}
@@ -234,6 +247,33 @@ protected function getConnection()
234247
return $this->ldapConnection;
235248
}
236249

250+
/**
251+
* Configure TLS CA certs globally for ldap use.
252+
* This will detect if the given path is a directory or file, and set the relevant
253+
* LDAP TLS options appropriately otherwise throw an exception if no file/folder found.
254+
*
255+
* Note: When using a folder, certificates are expected to be correctly named by hash
256+
* which can be done via the c_rehash utility.
257+
*
258+
* @throws LdapException
259+
*/
260+
protected function configureTlsCaCerts(string $caCertPath): void
261+
{
262+
$errMessage = "Provided path [{$caCertPath}] for LDAP TLS CA certs could not be resolved to an existing location";
263+
$path = realpath($caCertPath);
264+
if ($path === false) {
265+
throw new LdapException($errMessage);
266+
}
267+
268+
if (is_dir($path)) {
269+
$this->ldap->setOption(null, LDAP_OPT_X_TLS_CACERTDIR, $path);
270+
} else if (is_file($path)) {
271+
$this->ldap->setOption(null, LDAP_OPT_X_TLS_CACERTFILE, $path);
272+
} else {
273+
throw new LdapException($errMessage);
274+
}
275+
}
276+
237277
/**
238278
* Parse an LDAP server string and return the host suitable for a connection.
239279
* Is flexible to formats such as 'ldap.example.com:8069' or 'ldaps://ldap.example.com'.
@@ -249,13 +289,18 @@ protected function parseServerString(string $serverString): string
249289

250290
/**
251291
* Build a filter string by injecting common variables.
292+
* Both "${var}" and "{var}" style placeholders are supported.
293+
* Dollar based are old format but supported for compatibility.
252294
*/
253295
protected function buildFilter(string $filterString, array $attrs): string
254296
{
255297
$newAttrs = [];
256298
foreach ($attrs as $key => $attrText) {
257-
$newKey = '${' . $key . '}';
258-
$newAttrs[$newKey] = $this->ldap->escape($attrText);
299+
$escapedText = $this->ldap->escape($attrText);
300+
$oldVarKey = '${' . $key . '}';
301+
$newVarKey = '{' . $key . '}';
302+
$newAttrs[$oldVarKey] = $escapedText;
303+
$newAttrs[$newVarKey] = $escapedText;
259304
}
260305

261306
return strtr($filterString, $newAttrs);

app/Access/Mfa/TotpValidationRule.php

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,26 @@
22

33
namespace BookStack\Access\Mfa;
44

5-
use Illuminate\Contracts\Validation\Rule;
5+
use Closure;
6+
use Illuminate\Contracts\Validation\ValidationRule;
67

7-
class TotpValidationRule implements Rule
8+
class TotpValidationRule implements ValidationRule
89
{
9-
protected $secret;
10-
protected $totpService;
11-
1210
/**
1311
* Create a new rule instance.
1412
* Takes the TOTP secret that must be system provided, not user provided.
1513
*/
16-
public function __construct(string $secret)
17-
{
18-
$this->secret = $secret;
19-
$this->totpService = app()->make(TotpService::class);
14+
public function __construct(
15+
protected string $secret,
16+
protected TotpService $totpService,
17+
) {
2018
}
2119

22-
/**
23-
* Determine if the validation rule passes.
24-
*/
25-
public function passes($attribute, $value)
26-
{
27-
return $this->totpService->verifyCode($value, $this->secret);
28-
}
29-
30-
/**
31-
* Get the validation error message.
32-
*/
33-
public function message()
20+
public function validate(string $attribute, mixed $value, Closure $fail): void
3421
{
35-
return trans('validation.totp');
22+
$passes = $this->totpService->verifyCode($value, $this->secret);
23+
if (!$passes) {
24+
$fail(trans('validation.totp'));
25+
}
3626
}
3727
}

0 commit comments

Comments
 (0)