Update to Kirby 4.7.0

This commit is contained in:
Paul Nicoué 2025-04-21 18:57:21 +02:00
parent 02a9ab387c
commit ba25a9a198
509 changed files with 26604 additions and 14872 deletions

View file

@ -32,55 +32,37 @@ class Auth
/**
* Available auth challenge classes
* from the core and plugins
*
* @var array
*/
public static $challenges = [];
public static array $challenges = [];
/**
* Currently impersonated user
*
* @var \Kirby\Cms\User|null
*/
protected $impersonate;
/**
* Kirby instance
*
* @var \Kirby\Cms\App
*/
protected $kirby;
protected User|null $impersonate = null;
/**
* Cache of the auth status object
*
* @var \Kirby\Cms\Auth\Status
*/
protected $status;
protected Status|null $status = null;
/**
* Instance of the currently logged in user or
* `false` if the user was not yet determined
*
* @var \Kirby\Cms\User|null|false
*/
protected $user = false;
protected User|false|null $user = false;
/**
* Exception that was thrown while
* determining the current user
*
* @var \Throwable
*/
protected $userException;
protected Throwable|null $userException = null;
/**
* @param \Kirby\Cms\App $kirby
* @codeCoverageIgnore
*/
public function __construct(App $kirby)
{
$this->kirby = $kirby;
public function __construct(
protected App $kirby
) {
}
/**
@ -88,17 +70,18 @@ class Auth
* (one-time auth code)
* @since 3.5.0
*
* @param string $email
* @param bool $long If `true`, a long session will be created
* @param string $mode Either 'login' or 'password-reset'
* @return \Kirby\Cms\Auth\Status
* @param 'login'|'password-reset'|'2fa' $mode Purpose of the code
*
* @throws \Kirby\Exception\LogicException If there is no suitable authentication challenge (only in debug mode)
* @throws \Kirby\Exception\NotFoundException If the user does not exist (only in debug mode)
* @throws \Kirby\Exception\PermissionException If the rate limit is exceeded
*/
public function createChallenge(string $email, bool $long = false, string $mode = 'login')
{
public function createChallenge(
string $email,
bool $long = false,
string $mode = 'login'
): Status {
$email = Idn::decodeEmail($email);
$session = $this->kirby->session([
@ -145,7 +128,10 @@ class Auth
$session->set('kirby.challenge.type', $challenge);
if ($code !== null) {
$session->set('kirby.challenge.code', password_hash($code, PASSWORD_DEFAULT));
$session->set(
'kirby.challenge.code',
password_hash($code, PASSWORD_DEFAULT)
);
}
break;
@ -179,10 +165,8 @@ class Auth
/**
* Returns the csrf token if it exists and if it is valid
*
* @return string|false
*/
public function csrf()
public function csrf(): string|false
{
// get the csrf from the header
$fromHeader = $this->kirby->request()->csrf();
@ -201,8 +185,6 @@ class Auth
/**
* Returns either predefined csrf or the one from session
* @since 3.6.0
*
* @return string
*/
public function csrfFromSession(): string
{
@ -217,11 +199,10 @@ class Auth
* valid credentials
*
* @param \Kirby\Http\Request\Auth\BasicAuth|null $auth
* @return \Kirby\Cms\User|null
* @throws \Kirby\Exception\InvalidArgumentException if the authorization header is invalid
* @throws \Kirby\Exception\PermissionException if basic authentication is not allowed
*/
public function currentUserFromBasicAuth(BasicAuth $auth = null)
public function currentUserFromBasicAuth(BasicAuth|null $auth = null): User|null
{
if ($this->kirby->option('api.basicAuth', false) !== true) {
throw new PermissionException('Basic authentication is not activated');
@ -240,8 +221,8 @@ class Auth
}
}
$request = $this->kirby->request();
$auth = $auth ?? $request->auth();
$request = $this->kirby->request();
$auth ??= $request->auth();
if (!$auth || $auth->type() !== 'basic') {
throw new InvalidArgumentException('Invalid authorization header');
@ -257,10 +238,8 @@ class Auth
/**
* Returns the currently impersonated user
*
* @return \Kirby\Cms\User|null
*/
public function currentUserFromImpersonation()
public function currentUserFromImpersonation(): User|null
{
return $this->impersonate;
}
@ -269,12 +248,10 @@ class Auth
* Returns the logged in user by checking
* the current session and finding a valid
* valid user id in there
*
* @param \Kirby\Session\Session|array|null $session
* @return \Kirby\Cms\User|null
*/
public function currentUserFromSession($session = null)
{
public function currentUserFromSession(
Session|array|null $session = null
): User|null {
$session = $this->session($session);
$id = $session->data()->get('kirby.userId');
@ -318,12 +295,12 @@ class Auth
* Returns the list of enabled challenges in the
* configured order
* @since 3.5.1
*
* @return array
*/
public function enabledChallenges(): array
{
return A::wrap($this->kirby->option('auth.challenges', ['email']));
return A::wrap(
$this->kirby->option('auth.challenges', ['totp', 'email'])
);
}
/**
@ -333,10 +310,9 @@ class Auth
* `null` to use the actual user again,
* `'kirby'` for a virtual admin user or
* `'nobody'` to disable the actual user
* @return \Kirby\Cms\User|null
* @throws \Kirby\Exception\NotFoundException if the given user cannot be found
*/
public function impersonate(string|null $who = null)
public function impersonate(string|null $who = null): User|null
{
// clear the status cache
$this->status = null;
@ -360,8 +336,6 @@ class Auth
/**
* Returns the hashed ip of the visitor
* which is used to track invalid logins
*
* @return string
*/
public function ipHash(): string
{
@ -373,9 +347,6 @@ class Auth
/**
* Check if logins are blocked for the current ip or email
*
* @param string $email
* @return bool
*/
public function isBlocked(string $email): bool
{
@ -450,16 +421,12 @@ class Auth
/**
* Sets a user object as the current user in the cache
* @internal
*
* @param \Kirby\Cms\User $user
* @return void
*/
public function setUser(User $user): void
{
// stop impersonating
$this->impersonate = null;
$this->user = $user;
$this->user = $user;
// clear the status cache
$this->status = null;
@ -469,13 +436,13 @@ class Auth
* Returns the authentication status object
* @since 3.5.1
*
* @param \Kirby\Session\Session|array|null $session
* @param bool $allowImpersonation If set to false, only the actually
* logged in user will be returned
* @return \Kirby\Cms\Auth\Status
*/
public function status($session = null, bool $allowImpersonation = true)
{
public function status(
Session|array|null $session = null,
bool $allowImpersonation = true
): Status {
// try to return from cache
if ($this->status && $session === null && $allowImpersonation === true) {
return $this->status;
@ -588,8 +555,6 @@ class Auth
/**
* Returns the absolute path to the logins log
*
* @return string
*/
public function logfile(): string
{
@ -598,8 +563,6 @@ class Auth
/**
* Read all tracked logins
*
* @return array
*/
public function log(): array
{
@ -612,8 +575,8 @@ class Auth
}
// ensure that the category arrays are defined
$log['by-ip'] = $log['by-ip'] ?? [];
$log['by-email'] = $log['by-email'] ?? [];
$log['by-ip'] ??= [];
$log['by-email'] ??= [];
// remove all elements on the top level with different keys (old structure)
$log = array_intersect_key($log, array_flip(['by-ip', 'by-email']));
@ -642,8 +605,6 @@ class Auth
/**
* Logout the current user
*
* @return void
*/
public function logout(): void
{
@ -668,8 +629,6 @@ class Auth
/**
* Clears the cached user data after logout
* @internal
*
* @return void
*/
public function flush(): void
{
@ -681,12 +640,12 @@ class Auth
/**
* Tracks a login
*
* @param string|null $email
* @param bool $triggerHook If `false`, no user.login:failed hook is triggered
* @return bool
*/
public function track(string|null $email, bool $triggerHook = true): bool
{
public function track(
string|null $email,
bool $triggerHook = true
): bool {
if ($triggerHook === true) {
$this->kirby->trigger('user.login:failed', compact('email'));
}
@ -730,7 +689,6 @@ class Auth
* @param bool $allowImpersonation If set to false, 'impersonate' won't
* be returned as authentication type
* even if an impersonation is active
* @return string
*/
public function type(bool $allowImpersonation = true): string
{
@ -759,15 +717,15 @@ class Auth
/**
* Validates the currently logged in user
*
* @param \Kirby\Session\Session|array|null $session
* @param bool $allowImpersonation If set to false, only the actually
* logged in user will be returned
* @return \Kirby\Cms\User|null
*
* @throws \Throwable If an authentication error occurred
*/
public function user($session = null, bool $allowImpersonation = true)
{
public function user(
Session|array|null $session = null,
bool $allowImpersonation = true
): User|null {
if ($allowImpersonation === true && $this->impersonate !== null) {
return $this->impersonate;
}
@ -820,7 +778,7 @@ class Auth
public function verifyChallenge(
#[SensitiveParameter]
string $code
) {
): User {
try {
$session = $this->kirby->session();
@ -887,9 +845,11 @@ class Auth
throw new PermissionException(['key' => 'access.code']);
}
throw new LogicException('Invalid authentication challenge: ' . $challenge);
throw new LogicException(
'Invalid authentication challenge: ' . $challenge
);
} catch (Throwable $e) {
$details = $e instanceof \Kirby\Exception\Exception ? $e->getDetails() : [];
$details = $e instanceof Exception ? $e->getDetails() : [];
if (
empty($email) === false &&
@ -925,8 +885,10 @@ class Auth
* @throws \Throwable Either the passed `$exception` or the `$fallback`
* (no exception if debugging is disabled and no fallback was passed)
*/
protected function fail(Throwable $exception, Throwable $fallback = null): void
{
protected function fail(
Throwable $exception,
Throwable|null $fallback = null
): void {
$debug = $this->kirby->option('auth.debug', 'log');
// throw the original exception only in debug mode
@ -948,11 +910,8 @@ class Auth
/**
* Creates a session object from the passed options
*
* @param \Kirby\Session\Session|array|null $session
* @return \Kirby\Session\Session
*/
protected function session($session = null)
protected function session(Session|array|null $session = null): Session
{
// use passed session options or session object if set
if (is_array($session) === true) {