Upgrade to 3.9.6

This commit is contained in:
Bastian Allgeier
2023-07-27 12:08:43 +02:00
parent f76fbaa53e
commit 7928c28702
58 changed files with 930 additions and 148 deletions

View File

@@ -367,14 +367,14 @@ class App
* @return \Kirby\Toolkit\Collection|null
* @todo 5.0 Add return type declaration
*/
public function collection(string $name)
public function collection(string $name, array $options = [])
{
return $this->collections()->get($name, [
return $this->collections()->get($name, array_merge($options, [
'kirby' => $this,
'site' => $this->site(),
'pages' => $this->site()->children(),
'site' => $site = $this->site(),
'pages' => $site->children(),
'users' => $this->users()
]);
]));
}
/**
@@ -580,7 +580,14 @@ class App
$visitor = $this->visitor();
foreach ($visitor->acceptedLanguages() as $acceptedLang) {
$closure = fn ($language) => $language->locale(LC_ALL) === $acceptedLang->locale();
$closure = function ($language) use ($acceptedLang) {
$languageLocale = $language->locale(LC_ALL);
$acceptedLocale = $acceptedLang->locale();
return $languageLocale === $acceptedLocale ||
$acceptedLocale === Str::substr($languageLocale, 0, 2);
};
if ($language = $languages->filter($closure)?->first()) {
return $language;
}

View File

@@ -279,18 +279,39 @@ class Auth
$id = $session->data()->get('kirby.userId');
// if no user is logged in, return immediately
if (is_string($id) !== true) {
return null;
}
if ($user = $this->kirby->users()->find($id)) {
// in case the session needs to be updated, do it now
// for better performance
$session->commit();
return $user;
// a user is logged in, ensure it exists
$user = $this->kirby->users()->find($id);
if ($user === null) {
return null;
}
return null;
if ($passwordTimestamp = $user->passwordTimestamp()) {
$loginTimestamp = $session->data()->get('kirby.loginTimestamp');
if (is_int($loginTimestamp) !== true) {
// session that was created before Kirby
// 3.5.8.3, 3.6.6.3, 3.7.5.2, 3.8.4.1 or 3.9.6
// or when the user didn't have a password set
$user->logout();
return null;
}
// invalidate the session if the password
// changed since the login
if ($loginTimestamp < $passwordTimestamp) {
$user->logout();
return null;
}
}
// in case the session needs to be updated, do it now
// for better performance
$session->commit();
return $user;
}
/**

View File

@@ -121,7 +121,10 @@ class Find
'site' => $kirby->site(),
'account' => static::user(),
'page' => static::page(basename($path)),
'file' => static::file(...explode('/files/', $path)),
// regular expression to split the path at the last
// occurrence of /files/ which separates parent path
// and filename
'file' => static::file(...preg_split('$.*\K(/files/)$', $path)),
'user' => $kirby->user(basename($path)),
default => throw new InvalidArgumentException('Invalid model type: ' . $modelType)
};

View File

@@ -441,6 +441,9 @@ class User extends ModelWithContent
$session->regenerateToken(); // privilege change
$session->data()->set('kirby.userId', $this->id());
if ($this->passwordTimestamp() !== null) {
$session->data()->set('kirby.loginTimestamp', time());
}
$this->kirby()->auth()->setUser($this);
$kirby->trigger('user.login:after', ['user' => $this, 'session' => $session]);
@@ -461,6 +464,7 @@ class User extends ModelWithContent
// remove the user from the session for future requests
$session->data()->remove('kirby.userId');
$session->data()->remove('kirby.loginTimestamp');
// clear the cached user object from the app state of the current request
$this->kirby()->auth()->flush();
@@ -607,6 +611,26 @@ class User extends ModelWithContent
return $this->password = $this->readPassword();
}
/**
* Returns the timestamp when the password
* was last changed
*/
public function passwordTimestamp(): int|null
{
$file = $this->passwordFile();
// ensure we have the latest information
// to prevent cache attacks
clearstatcache();
// user does not have a password
if (is_file($file) === false) {
return null;
}
return filemtime($file);
}
/**
* @return \Kirby\Cms\UserPermissions
*/
@@ -864,14 +888,29 @@ class User extends ModelWithContent
throw new NotFoundException(['key' => 'user.password.undefined']);
}
// `UserRules` enforces a minimum length of 8 characters,
// so everything below that is a typo
if (Str::length($password) < 8) {
throw new InvalidArgumentException(['key' => 'user.password.invalid']);
}
// too long passwords can cause DoS attacks
if (Str::length($password) > 1000) {
throw new InvalidArgumentException(['key' => 'user.password.excessive']);
}
if (password_verify($password, $this->password()) !== true) {
throw new InvalidArgumentException(['key' => 'user.password.wrong', 'httpCode' => 401]);
}
return true;
}
/**
* Returns the path to the password file
*/
protected function passwordFile(): string
{
return $this->root() . '/.htpasswd';
}
}

View File

@@ -118,6 +118,13 @@ trait UserActions
// update the users collection
$user->kirby()->users()->set($user->id(), $user);
// keep the user logged in to the current browser
// if they changed their own password
// (regenerate the session token, update the login timestamp)
if ($user->isLoggedIn() === true) {
$user->loginPasswordless();
}
return $user;
});
}
@@ -323,7 +330,7 @@ trait UserActions
*/
protected function readPassword()
{
return F::read($this->root() . '/.htpasswd');
return F::read($this->passwordFile());
}
/**
@@ -384,6 +391,6 @@ trait UserActions
#[SensitiveParameter]
string $password = null
): bool {
return F::write($this->root() . '/.htpasswd', $password);
return F::write($this->passwordFile(), $password);
}
}

View File

@@ -341,12 +341,23 @@ class UserRules
#[SensitiveParameter]
string $password
): bool {
// too short passwords are ineffective
if (Str::length($password ?? null) < 8) {
throw new InvalidArgumentException([
'key' => 'user.password.invalid',
]);
}
// too long passwords can cause DoS attacks
// and are therefore blocked in the auth system
// (blocked here as well to avoid passwords
// that cannot be used to log in)
if (Str::length($password ?? null) > 1000) {
throw new InvalidArgumentException([
'key' => 'user.password.excessive',
]);
}
return true;
}