Upgrade to 3.2.3

This commit is contained in:
Bastian Allgeier
2019-08-06 10:24:12 +02:00
parent 40f095f5de
commit 02be32bb75
48 changed files with 356 additions and 114 deletions

View File

@@ -1,7 +1,7 @@
{ {
"name": "getkirby/cms", "name": "getkirby/cms",
"description": "The Kirby 3 core", "description": "The Kirby 3 core",
"version": "3.2.2", "version": "3.2.3",
"license": "proprietary", "license": "proprietary",
"keywords": ["kirby", "cms", "core"], "keywords": ["kirby", "cms", "core"],
"homepage": "https://getkirby.com", "homepage": "https://getkirby.com",

View File

@@ -35,7 +35,7 @@ return [
'requirements' => function (System $system) { 'requirements' => function (System $system) {
return $system->toArray(); return $system->toArray();
}, },
'breadcrumbTitle' => function () { 'site' => function () {
try { try {
return $this->site()->blueprint()->title(); return $this->site()->blueprint()->title();
} catch (Throwable $e) { } catch (Throwable $e) {
@@ -90,7 +90,6 @@ return [
], ],
'panel' => [ 'panel' => [
'ascii', 'ascii',
'breadcrumbTitle',
'isOk', 'isOk',
'isInstalled', 'isInstalled',
'isLocal', 'isLocal',
@@ -99,6 +98,7 @@ return [
'license', 'license',
'multilang', 'multilang',
'requirements', 'requirements',
'site',
'slugs', 'slugs',
'title', 'title',
'translation', 'translation',

View File

@@ -35,25 +35,13 @@ return [
$long = $this->requestBody('long'); $long = $this->requestBody('long');
$password = $this->requestBody('password'); $password = $this->requestBody('password');
try { $user = $this->kirby()->auth()->login($email, $password, $long);
if ($user = $this->kirby()->auth()->login($email, $password, $long)) {
return [
'code' => 200,
'status' => 'ok',
'user' => $this->resolve($user)->view('auth')->toArray()
];
}
throw new NotFoundException(['key' => 'user.undefined']); return [
} catch (Throwable $e) { 'code' => 200,
if ($this->kirby()->option('debug') === true) { 'status' => 'ok',
throw $e; 'user' => $this->resolve($user)->view('auth')->toArray()
} else { ];
// catch any kind of login error
}
}
throw new InvalidArgumentException('Invalid email or password');
} }
], ],
[ [

View File

@@ -1,37 +1,70 @@
<?php <?php
use Kirby\Exception\Exception;
/** /**
* Content Lock Routes * Content Lock Routes
*/ */
return [ return [
[ [
'pattern' => '(:all)/lock', 'pattern' => '(:all)/lock',
'method' => 'GET', 'method' => 'GET',
'action' => function (string $path) { 'action' => function (string $path) {
return $this->parent($path)->lock()->get(); if ($lock = $this->parent($path)->lock()) {
return [
'supported' => true,
'locked' => $lock->get()
];
}
return [
'supported' => false,
'locked' => null
];
} }
], ],
[ [
'pattern' => '(:all)/lock', 'pattern' => '(:all)/lock',
'method' => 'PATCH', 'method' => 'PATCH',
'action' => function (string $path) { 'action' => function (string $path) {
return $this->parent($path)->lock()->create(); if ($lock = $this->parent($path)->lock()) {
return $lock->create();
}
throw new Exception([
'key' => 'lock.notImplemented',
'httpCode' => 501
]);
} }
], ],
[ [
'pattern' => '(:all)/lock', 'pattern' => '(:all)/lock',
'method' => 'DELETE', 'method' => 'DELETE',
'action' => function (string $path) { 'action' => function (string $path) {
return $this->parent($path)->lock()->remove(); if ($lock = $this->parent($path)->lock()) {
return $lock->remove();
}
throw new Exception([
'key' => 'lock.notImplemented',
'httpCode' => 501
]);
} }
], ],
[ [
'pattern' => '(:all)/unlock', 'pattern' => '(:all)/unlock',
'method' => 'GET', 'method' => 'GET',
'action' => function (string $path) { 'action' => function (string $path) {
if ($lock = $this->parent($path)->lock()) {
return [
'supported' => true,
'unlocked' => $lock->isUnlocked()
];
}
return [ return [
'isUnlocked' => $this->parent($path)->lock()->isUnlocked() 'supported' => false,
'unlocked' => null
]; ];
} }
], ],
@@ -39,14 +72,28 @@ return [
'pattern' => '(:all)/unlock', 'pattern' => '(:all)/unlock',
'method' => 'PATCH', 'method' => 'PATCH',
'action' => function (string $path) { 'action' => function (string $path) {
return $this->parent($path)->lock()->unlock(); if ($lock = $this->parent($path)->lock()) {
return $lock->unlock();
}
throw new Exception([
'key' => 'lock.notImplemented',
'httpCode' => 501
]);
} }
], ],
[ [
'pattern' => '(:all)/unlock', 'pattern' => '(:all)/unlock',
'method' => 'DELETE', 'method' => 'DELETE',
'action' => function (string $path) { 'action' => function (string $path) {
return $this->parent($path)->lock()->resolve(); if ($lock = $this->parent($path)->lock()) {
return $lock->resolve();
}
throw new Exception([
'key' => 'lock.notImplemented',
'httpCode' => 501
]);
} }
], ],
]; ];

View File

@@ -82,7 +82,7 @@ return [
$files = $this->parent->files()->template($this->template); $files = $this->parent->files()->template($this->template);
if ($this->sortBy) { if ($this->sortBy) {
$files = $files->sortBy(...Str::split($this->sortBy, ' ')); $files = $files->sortBy(...$files::sortArgs($this->sortBy));
} elseif ($this->sortable === true) { } elseif ($this->sortable === true) {
$files = $files->sortBy('sort', 'asc'); $files = $files->sortBy('sort', 'asc');
} }

View File

@@ -122,7 +122,7 @@ return [
// sort // sort
if ($this->sortBy) { if ($this->sortBy) {
$pages = $pages->sortBy(...Str::split($this->sortBy, ' ')); $pages = $pages->sortBy(...$pages::sortArgs($this->sortBy));
} }
// pagination // pagination

View File

@@ -106,7 +106,7 @@ return [
if (empty($tag->link) === true) { if (empty($tag->link) === true) {
return $img; return $img;
} }
if ($link = $tag->file($tag->link)) { if ($link = $tag->file($tag->link)) {
$link = $link->url(); $link = $link->url();
} else { } else {
@@ -227,13 +227,15 @@ return [
'html' => function ($tag) { 'html' => function ($tag) {
$video = Html::video( $video = Html::video(
$tag->value, $tag->value,
$tag->kirby()->option('kirbytext.video.options', []) $tag->kirby()->option('kirbytext.video.options', []),
[
'height' => $tag->height ?? $tag->kirby()->option('kirbytext.video.height'),
'width' => $tag->width ?? $tag->kirby()->option('kirbytext.video.width'),
]
); );
return Html::figure([$video], $tag->caption, [ return Html::figure([$video], $tag->caption, [
'class' => $tag->class ?? $tag->kirby()->option('kirbytext.video.class', 'video'), 'class' => $tag->class ?? $tag->kirby()->option('kirbytext.video.class', 'video'),
'height' => $tag->height ?? $tag->kirby()->option('kirbytext.video.height'),
'width' => $tag->width ?? $tag->kirby()->option('kirbytext.video.width'),
]); ]);
} }
], ],

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Отляво надясно", "language.direction.ltr": "Отляво надясно",
"language.direction.rtl": "Отдясно наляво", "language.direction.rtl": "Отдясно наляво",
"language.locale": "PHP locale string", "language.locale": "PHP locale string",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Име", "language.name": "Име",
"language.updated": "Езикът беше обновен", "language.updated": "Езикът беше обновен",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Esquerra a dreta", "language.direction.ltr": "Esquerra a dreta",
"language.direction.rtl": "De dreta a esquerra", "language.direction.rtl": "De dreta a esquerra",
"language.locale": "Cadena local de PHP", "language.locale": "Cadena local de PHP",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Nom", "language.name": "Nom",
"language.updated": "S'ha actualitzat l'idioma", "language.updated": "S'ha actualitzat l'idioma",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Zleva doprava", "language.direction.ltr": "Zleva doprava",
"language.direction.rtl": "Zprava doleva", "language.direction.rtl": "Zprava doleva",
"language.locale": "Řetězec lokalizace PHP", "language.locale": "Řetězec lokalizace PHP",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Jméno", "language.name": "Jméno",
"language.updated": "Jazyk byl aktualizován", "language.updated": "Jazyk byl aktualizován",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Venstre mod højre", "language.direction.ltr": "Venstre mod højre",
"language.direction.rtl": "Højre mod venstre", "language.direction.rtl": "Højre mod venstre",
"language.locale": "PHP locale string", "language.locale": "PHP locale string",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Navn", "language.name": "Navn",
"language.updated": "Sproget er blevet opdateret", "language.updated": "Sproget er blevet opdateret",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Von links nach rechts", "language.direction.ltr": "Von links nach rechts",
"language.direction.rtl": "Von rechts nach links", "language.direction.rtl": "Von rechts nach links",
"language.locale": "PHP locale string", "language.locale": "PHP locale string",
"language.locale.warning": "Du nutzt ein angepasstes Setup for PHP Locales. Bitte bearbeite dieses direkt in der entsprechenden Sprachdatei in /site/languages",
"language.name": "Name", "language.name": "Name",
"language.updated": "Die Sprache wurde gespeichert", "language.updated": "Die Sprache wurde gespeichert",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Αριστερά προς τα δεξιά", "language.direction.ltr": "Αριστερά προς τα δεξιά",
"language.direction.rtl": "Δεξιά προς τα αριστερά", "language.direction.rtl": "Δεξιά προς τα αριστερά",
"language.locale": "Συμβολοσειρά τοπικής γλώσσας PHP", "language.locale": "Συμβολοσειρά τοπικής γλώσσας PHP",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Ονομασία", "language.name": "Ονομασία",
"language.updated": "Η γλώσσα έχει ενημερωθεί", "language.updated": "Η γλώσσα έχει ενημερωθεί",

View File

@@ -169,7 +169,6 @@
"error.user.password.notSame": "The passwords do not match", "error.user.password.notSame": "The passwords do not match",
"error.user.password.undefined": "The user does not have a password", "error.user.password.undefined": "The user does not have a password",
"error.user.role.invalid": "Please enter a valid role", "error.user.role.invalid": "Please enter a valid role",
"error.user.undefined": "The user cannot be found",
"error.user.update.permission": "error.user.update.permission":
"You are not allowed to update the user \"{name}\"", "You are not allowed to update the user \"{name}\"",
@@ -265,6 +264,7 @@
"language.direction.ltr": "Left to right", "language.direction.ltr": "Left to right",
"language.direction.rtl": "Right to left", "language.direction.rtl": "Right to left",
"language.locale": "PHP locale string", "language.locale": "PHP locale string",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Name", "language.name": "Name",
"language.updated": "The language has been updated", "language.updated": "The language has been updated",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "De Izquierda a derecha", "language.direction.ltr": "De Izquierda a derecha",
"language.direction.rtl": "De derecha a izquierda", "language.direction.rtl": "De derecha a izquierda",
"language.locale": "Cadena de localización PHP", "language.locale": "Cadena de localización PHP",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Nombre", "language.name": "Nombre",
"language.updated": "El idioma a sido actualizado", "language.updated": "El idioma a sido actualizado",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "De izquierda a derecha", "language.direction.ltr": "De izquierda a derecha",
"language.direction.rtl": "De derecha a izquierda", "language.direction.rtl": "De derecha a izquierda",
"language.locale": "PHP locale string", "language.locale": "PHP locale string",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Nombre", "language.name": "Nombre",
"language.updated": "El idioma ha sido actualizado", "language.updated": "El idioma ha sido actualizado",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "چپ به راست", "language.direction.ltr": "چپ به راست",
"language.direction.rtl": "راست به چپ", "language.direction.rtl": "راست به چپ",
"language.locale": "PHP locale string", "language.locale": "PHP locale string",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "پارسی", "language.name": "پارسی",
"language.updated": "زبان به روز شد", "language.updated": "زبان به روز شد",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Vasemmalta oikealle", "language.direction.ltr": "Vasemmalta oikealle",
"language.direction.rtl": "Oikealta vasemmalle", "language.direction.rtl": "Oikealta vasemmalle",
"language.locale": "PHP-lokaalin tunniste", "language.locale": "PHP-lokaalin tunniste",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Nimi", "language.name": "Nimi",
"language.updated": "Kieli on päivitetty", "language.updated": "Kieli on päivitetty",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "De gauche à droite", "language.direction.ltr": "De gauche à droite",
"language.direction.rtl": "De droite à gauche", "language.direction.rtl": "De droite à gauche",
"language.locale": "Locales PHP", "language.locale": "Locales PHP",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Nom", "language.name": "Nom",
"language.updated": "La langue a été mise à jour", "language.updated": "La langue a été mise à jour",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Balról jobbra", "language.direction.ltr": "Balról jobbra",
"language.direction.rtl": "Jobbról balra", "language.direction.rtl": "Jobbról balra",
"language.locale": "PHP locale sztring", "language.locale": "PHP locale sztring",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Név", "language.name": "Név",
"language.updated": "A nyelv frissítve lett", "language.updated": "A nyelv frissítve lett",

View File

@@ -183,9 +183,9 @@
"error.validation.contains": "error.validation.contains":
"Masukkan nilai yang mengandung \"{needle}\"", "Masukkan nilai yang mengandung \"{needle}\"",
"error.validation.date": "Masukkan tanggal yang valid", "error.validation.date": "Masukkan tanggal yang valid",
"error.validation.date.after": "Please enter a date after {date}", "error.validation.date.after": "Masukkan tanggal setelah {date}",
"error.validation.date.before": "Please enter a date before {date}", "error.validation.date.before": "Masukkan tanggal sebelum {date}",
"error.validation.date.between": "Please enter a date between {min} and {max}", "error.validation.date.between": "Masukkan tanggal antara {min} dan {max}",
"error.validation.denied": "Mohon tolak", "error.validation.denied": "Mohon tolak",
"error.validation.different": "Nilai harus selain \"{other}\"", "error.validation.different": "Nilai harus selain \"{other}\"",
"error.validation.email": "Masukkan surel yang valid", "error.validation.email": "Masukkan surel yang valid",
@@ -265,6 +265,7 @@
"language.direction.ltr": "Kiri ke kanan", "language.direction.ltr": "Kiri ke kanan",
"language.direction.rtl": "Kanan ke kiri", "language.direction.rtl": "Kanan ke kiri",
"language.locale": "String \"PHP locale\"", "language.locale": "String \"PHP locale\"",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Nama", "language.name": "Nama",
"language.updated": "Bahasa sudah diperbaharui", "language.updated": "Bahasa sudah diperbaharui",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Sinistra a destra", "language.direction.ltr": "Sinistra a destra",
"language.direction.rtl": "Destra a sinistra", "language.direction.rtl": "Destra a sinistra",
"language.locale": "Stringa \"PHP locale\"", "language.locale": "Stringa \"PHP locale\"",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Nome", "language.name": "Nome",
"language.updated": "La lingua è stata aggiornata", "language.updated": "La lingua è stata aggiornata",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "왼쪽에서 오른쪽", "language.direction.ltr": "왼쪽에서 오른쪽",
"language.direction.rtl": "오른쪽에서 왼쪽", "language.direction.rtl": "오른쪽에서 왼쪽",
"language.locale": "PHP 로캘 문자열", "language.locale": "PHP 로캘 문자열",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "이름", "language.name": "이름",
"language.updated": "언어를 변경했습니다.", "language.updated": "언어를 변경했습니다.",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Venstre til høyre", "language.direction.ltr": "Venstre til høyre",
"language.direction.rtl": "Høyre til venstre", "language.direction.rtl": "Høyre til venstre",
"language.locale": "PHP locale streng", "language.locale": "PHP locale streng",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Navn", "language.name": "Navn",
"language.updated": "Språk har blitt oppdatert", "language.updated": "Språk har blitt oppdatert",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Links naar rechts", "language.direction.ltr": "Links naar rechts",
"language.direction.rtl": "Rechts naar links", "language.direction.rtl": "Rechts naar links",
"language.locale": "PHP-locale regel", "language.locale": "PHP-locale regel",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Naam", "language.name": "Naam",
"language.updated": "De taal is geüpdatet", "language.updated": "De taal is geüpdatet",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Od lewej do prawej", "language.direction.ltr": "Od lewej do prawej",
"language.direction.rtl": "Od prawej do lewej", "language.direction.rtl": "Od prawej do lewej",
"language.locale": "PHP locale string", "language.locale": "PHP locale string",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Nazwa", "language.name": "Nazwa",
"language.updated": "Język został zaktualizowany", "language.updated": "Język został zaktualizowany",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Esquerda para direita", "language.direction.ltr": "Esquerda para direita",
"language.direction.rtl": "Direita para esquerda", "language.direction.rtl": "Direita para esquerda",
"language.locale": "String de localização do PHP", "language.locale": "String de localização do PHP",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Nome", "language.name": "Nome",
"language.updated": "Idioma atualizado", "language.updated": "Idioma atualizado",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Esquerda para direita", "language.direction.ltr": "Esquerda para direita",
"language.direction.rtl": "Direita para esquerda", "language.direction.rtl": "Direita para esquerda",
"language.locale": "String de localização do PHP", "language.locale": "String de localização do PHP",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Nome", "language.name": "Nome",
"language.updated": "Idioma atualizado", "language.updated": "Idioma atualizado",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Zľava doprava", "language.direction.ltr": "Zľava doprava",
"language.direction.rtl": "Zprava doľava", "language.direction.rtl": "Zprava doľava",
"language.locale": "PHP locale string", "language.locale": "PHP locale string",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Názov", "language.name": "Názov",
"language.updated": "Jazyk bol aktualizovaný", "language.updated": "Jazyk bol aktualizovaný",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Vänster till höger", "language.direction.ltr": "Vänster till höger",
"language.direction.rtl": "Höger till vänster", "language.direction.rtl": "Höger till vänster",
"language.locale": "PHP locale string", "language.locale": "PHP locale string",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Namn", "language.name": "Namn",
"language.updated": "Språket har uppdaterats", "language.updated": "Språket har uppdaterats",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Soldan sağa", "language.direction.ltr": "Soldan sağa",
"language.direction.rtl": "Sağdan sola", "language.direction.rtl": "Sağdan sola",
"language.locale": "PHP yerel dizesi", "language.locale": "PHP yerel dizesi",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "İsim", "language.name": "İsim",
"language.updated": "Dil güncellendi", "language.updated": "Dil güncellendi",

File diff suppressed because one or more lines are too long

View File

@@ -543,7 +543,7 @@ class Api
if (is_a($e, 'Kirby\Exception\Exception') === true) { if (is_a($e, 'Kirby\Exception\Exception') === true) {
$result = [ $result = [
'status' => 'error', 'status' => 'error',
'route' => $this->route->pattern() 'route' => ($this->route)? $this->route->pattern() : null
] + $e->toArray(); ] + $e->toArray();
} else { } else {
// remove the document root from the file path // remove the document root from the file path

View File

@@ -81,7 +81,7 @@ trait AppCaches
]; ];
} }
$prefix = str_replace('/', '_', $this->system()->indexUrl()) . $prefix = str_replace(['/', ':'], '_', $this->system()->indexUrl()) .
'/' . '/' .
str_replace('.', '/', $key); str_replace('.', '/', $key);

View File

@@ -2,6 +2,8 @@
namespace Kirby\Cms; namespace Kirby\Cms;
use Throwable;
/** /**
* AppUsers * AppUsers
* *
@@ -88,7 +90,11 @@ trait AppUsers
if (is_string($this->user) === true) { if (is_string($this->user) === true) {
return $this->auth()->impersonate($this->user); return $this->auth()->impersonate($this->user);
} else { } else {
return $this->auth()->user(); try {
return $this->auth()->user();
} catch (Throwable $e) {
return null;
}
} }
} }

View File

@@ -7,6 +7,7 @@ use Kirby\Exception\PermissionException;
use Kirby\Exception\InvalidArgumentException; use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\NotFoundException; use Kirby\Exception\NotFoundException;
use Kirby\Http\Request\Auth\BasicAuth; use Kirby\Http\Request\Auth\BasicAuth;
use Kirby\Toolkit\F;
use Throwable; use Throwable;
/** /**
@@ -22,10 +23,12 @@ class Auth
{ {
protected $impersonate; protected $impersonate;
protected $kirby; protected $kirby;
protected $user; protected $user = false;
protected $userException;
/** /**
* @param Kirby\Cms\App $kirby * @param Kirby\Cms\App $kirby
* @codeCoverageIgnore
*/ */
public function __construct(App $kirby) public function __construct(App $kirby)
{ {
@@ -79,13 +82,7 @@ class Auth
throw new PermissionException('Basic authentication is only allowed over HTTPS'); throw new PermissionException('Basic authentication is only allowed over HTTPS');
} }
if ($user = $this->kirby->users()->find($auth->username())) { return $this->validatePassword($auth->username(), $auth->password());
if ($user->validatePassword($auth->password()) === true) {
return $user;
}
}
return null;
} }
/** /**
@@ -158,24 +155,33 @@ class Auth
*/ */
public function ipHash(): string public function ipHash(): string
{ {
return hash('sha256', $this->kirby->visitor()->ip()); $hash = hash('sha256', $this->kirby->visitor()->ip());
// only use the first 50 chars to ensure privacy
return substr($hash, 0, 50);
} }
/** /**
* Check if logins are blocked for the current ip * Check if logins are blocked for the current ip or email
* *
* @param string $email
* @return boolean * @return boolean
*/ */
public function isBlocked(): bool public function isBlocked(string $email): bool
{ {
$ip = $this->ipHash(); $ip = $this->ipHash();
$log = $this->log(); $log = $this->log();
$trials = $this->kirby->option('auth.trials', 10); $trials = $this->kirby->option('auth.trials', 10);
$timeout = $this->kirby->option('auth.timeout', 3600);
if ($entry = ($log[$ip] ?? null)) { if ($entry = ($log['by-ip'][$ip] ?? null)) {
if ($entry['trials'] > $trials) { if ($entry['trials'] >= $trials) {
if ($entry['time'] > (time() - $timeout)) { return true;
}
}
if ($this->kirby->users()->find($email)) {
if ($entry = ($log['by-email'][$email] ?? null)) {
if ($entry['trials'] >= $trials) {
return true; return true;
} }
} }
@@ -190,18 +196,14 @@ class Auth
* @param string $email * @param string $email
* @param string $password * @param string $password
* @param boolean $long * @param boolean $long
* @return Kirby\Cms\User|false * @return Kirby\Cms\User
*
* @throws PermissionException If the rate limit was exceeded or if any other error occured with debug mode off
* @throws NotFoundException If the email was invalid
* @throws InvalidArgumentException If the password is not valid (via `$user->login()`)
*/ */
public function login(string $email, string $password, bool $long = false) public function login(string $email, string $password, bool $long = false)
{ {
// check for blocked ips
if ($this->isBlocked() === true) {
throw new PermissionException('Rate limit exceeded', 403);
}
// stop impersonating
$this->impersonate = null;
// session options // session options
$options = [ $options = [
'createMode' => 'cookie', 'createMode' => 'cookie',
@@ -209,20 +211,71 @@ class Auth
]; ];
// validate the user and log in to the session // validate the user and log in to the session
if ($user = $this->kirby->users()->find($email)) { $user = $this->validatePassword($email, $password);
if ($user->login($password, $options) === true) { $user->loginPasswordless($options);
return $this->user = $user;
// stop impersonating
$this->impersonate = null;
return $this->user = $user;
}
/**
* Validates the user credentials and returns the user object on success;
* otherwise logs the failed attempt
*
* @param string $email
* @param string $password
* @return Kirby\Cms\User
*
* @throws PermissionException If the rate limit was exceeded or if any other error occured with debug mode off
* @throws NotFoundException If the email was invalid
* @throws InvalidArgumentException If the password is not valid (via `$user->login()`)
*/
public function validatePassword(string $email, string $password)
{
// check for blocked ips
if ($this->isBlocked($email) === true) {
if ($this->kirby->option('debug') === true) {
$message = 'Rate limit exceeded';
} else {
// avoid leaking security-relevant information
$message = 'Invalid email or password';
} }
throw new PermissionException($message, 403);
} }
// log invalid login trial // validate the user
$this->track(); try {
if ($user = $this->kirby->users()->find($email)) {
if ($user->validatePassword($password) === true) {
return $user;
}
}
// sleep for a random amount of milliseconds throw new NotFoundException([
// to make automated attacks harder 'key' => 'user.notFound',
usleep(random_int(1000, 2000000)); 'data' => [
'name' => $email
]
]);
} catch (Throwable $e) {
// log invalid login trial
$this->track($email);
return false; // sleep for a random amount of milliseconds
// to make automated attacks harder
usleep(random_int(1000, 2000000));
// keep throwing the original error in debug mode,
// otherwise hide it to avoid leaking security-relevant information
if ($this->kirby->option('debug') === true) {
throw $e;
} else {
throw new PermissionException('Invalid email or password');
}
}
} }
/** /**
@@ -243,10 +296,39 @@ class Auth
public function log(): array public function log(): array
{ {
try { try {
return Data::read($this->logfile(), 'json'); $log = Data::read($this->logfile(), 'json');
$read = true;
} catch (Throwable $e) { } catch (Throwable $e) {
return []; $log = [];
$read = false;
} }
// ensure that the category arrays are defined
$log['by-ip'] = $log['by-ip'] ?? [];
$log['by-email'] = $log['by-email'] ?? [];
// remove entries that are no longer needed
$originalLog = $log;
$time = time() - $this->kirby->option('auth.timeout', 3600);
foreach ($log as $category => $entries) {
$log[$category] = array_filter($entries, function ($entry) use ($time) {
return $entry['time'] > $time;
});
}
// remove all elements on the top level with different keys (old structure)
$log = array_intersect_key($log, array_flip(['by-ip', 'by-email']));
// write new log to the file system if it changed
if ($read === false || $log !== $originalLog) {
if (count($log['by-ip']) === 0 && count($log['by-email']) === 0) {
F::remove($this->logfile());
} else {
Data::write($this->logfile(), $log, 'json');
}
}
return $log;
} }
/** /**
@@ -271,26 +353,41 @@ class Auth
/** /**
* Tracks a login * Tracks a login
* *
* @param string $email
* @return boolean * @return boolean
*/ */
public function track(): bool public function track(string $email): bool
{ {
$ip = $this->ipHash(); $ip = $this->ipHash();
$log = $this->log(); $log = $this->log();
$time = time(); $time = time();
if (isset($log[$ip]) === true) { if (isset($log['by-ip'][$ip]) === true) {
$log[$ip] = [ $log['by-ip'][$ip] = [
'time' => $time, 'time' => $time,
'trials' => ($log[$ip]['trials'] ?? 0) + 1 'trials' => ($log['by-ip'][$ip]['trials'] ?? 0) + 1
]; ];
} else { } else {
$log[$ip] = [ $log['by-ip'][$ip] = [
'time' => $time, 'time' => $time,
'trials' => 1 'trials' => 1
]; ];
} }
if ($this->kirby->users()->find($email)) {
if (isset($log['by-email'][$email]) === true) {
$log['by-email'][$email] = [
'time' => $time,
'trials' => ($log['by-email'][$email]['trials'] ?? 0) + 1
];
} else {
$log['by-email'][$email] = [
'time' => $time,
'trials' => 1
];
}
}
return Data::write($this->logfile(), $log, 'json'); return Data::write($this->logfile(), $log, 'json');
} }
@@ -316,8 +413,9 @@ class Auth
/** /**
* Validates the currently logged in user * Validates the currently logged in user
* *
* @param Kirby\Session\Sessionarray||null $session * @param Kirby\Session\Session|array|null $session
* @return Kirby\Cms\User|null * @return Kirby\Cms\User
* @throws
*/ */
public function user($session = null) public function user($session = null)
{ {
@@ -325,6 +423,18 @@ class Auth
return $this->impersonate; return $this->impersonate;
} }
// return from cache
if ($this->user === null) {
// throw the same Exception again if one was captured before
if ($this->userException !== null) {
throw $this->userException;
}
return null;
} elseif ($this->user !== false) {
return $this->user;
}
try { try {
if ($this->type() === 'basic') { if ($this->type() === 'basic') {
return $this->user = $this->currentUserFromBasicAuth(); return $this->user = $this->currentUserFromBasicAuth();
@@ -332,7 +442,12 @@ class Auth
return $this->user = $this->currentUserFromSession($session); return $this->user = $this->currentUserFromSession($session);
} }
} catch (Throwable $e) { } catch (Throwable $e) {
return $this->user = null; $this->user = null;
// capture the Exception for future calls
$this->userException = $e;
throw $e;
} }
} }
} }

View File

@@ -66,12 +66,12 @@ class ContentLock
} }
/** /**
* Returns array with `locked` flag and, * Returns either `false` or array with `user`, `email`,
* if needed, `user`, `email`, `time`, `canUnlock` * `time` and `unlockable` keys
* *
* @return array * @return array|bool
*/ */
public function get(): array public function get()
{ {
$data = $this->data['lock'] ?? []; $data = $this->data['lock'] ?? [];
@@ -83,17 +83,14 @@ class ContentLock
$time = intval($data['time']); $time = intval($data['time']);
return [ return [
'locked' => true, 'user' => $user->id(),
'user' => $user->id(), 'email' => $user->email(),
'email' => $user->email(), 'time' => $time,
'time' => $time, 'unlockable' => $time + $this->kirby()->option('lock.duration', 60 * 2) <= time()
'canUnlock' => $time + $this->kirby()->option('lock.duration', 60 * 2) <= time()
]; ];
} }
return [ return false;
'locked' => false
];
} }
/** /**
@@ -105,10 +102,7 @@ class ContentLock
{ {
$lock = $this->get(); $lock = $this->get();
if ( if ($lock !== false && $lock['user'] !== $this->user()->id()) {
$lock['locked'] === true &&
$lock['user'] !== $this->user()->id()
) {
return true; return true;
} }

View File

@@ -260,11 +260,18 @@ abstract class ModelWithContent extends Model
/** /**
* Returns the lock object for this model * Returns the lock object for this model
* *
* @return Kirby\Cms\ContentLock * Only if a content directory exists,
* virtual pages will need to overwrite this method
*
* @return Kirby\Cms\ContentLock|null
*/ */
public function lock() public function lock()
{ {
return new ContentLock($this); $dir = $this->contentFileDirectory();
if (is_string($dir) === true && file_exists($dir) === true) {
return new ContentLock($this);
}
} }
/** /**

View File

@@ -420,7 +420,7 @@ class User extends ModelWithContent
/** /**
* Logs the user in without checking the password * Logs the user in without checking the password
* *
* @param SKirby\Session\Session|array $session Session options or session object to set the user in * @param Kirby\Session\Session|array $session Session options or session object to set the user in
* @return void * @return void
*/ */
public function loginPasswordless($session = null): void public function loginPasswordless($session = null): void

View File

@@ -109,8 +109,15 @@ class Users extends Collection
continue; continue;
} }
$user = new User([ // get role information
'id' => $userDirectory, if (file_exists($root . '/' . $userDirectory . '/index.php') === true) {
$credentials = require $root . '/' . $userDirectory . '/index.php';
}
// create user model based on role
$user = User::factory([
'id' => $userDirectory,
'model' => $credentials['role'] ?? null
] + $inject); ] + $inject);
$users->set($user->id(), $user); $users->set($user->id(), $user);

View File

@@ -247,7 +247,7 @@ class Uri
} }
$uri = Server::get('REQUEST_URI'); $uri = Server::get('REQUEST_URI');
$uri = str_replace(Server::get('HTTP_HOST'), '', $uri); $uri = preg_replace('!^(http|https)\:\/\/' . Server::get('HTTP_HOST') . '!', '', $uri);
$uri = parse_url('http://getkirby.com' . $uri); $uri = parse_url('http://getkirby.com' . $uri);
$url = new static(array_merge([ $url = new static(array_merge([

View File

@@ -88,7 +88,7 @@ class Image extends File
return $this->dimensions; return $this->dimensions;
} }
if (in_array($this->mime(), ['image/jpeg', 'image/jp2', 'image/png', 'image/gif'])) { if (in_array($this->mime(), ['image/jpeg', 'image/jp2', 'image/png', 'image/gif', 'image/webp'])) {
return $this->dimensions = Dimensions::forImage($this->root); return $this->dimensions = Dimensions::forImage($this->root);
} }

View File

@@ -469,6 +469,9 @@ class Session
} else { } else {
$this->needsRetransmission = true; $this->needsRetransmission = true;
} }
// update cache of the Sessions instance with the new token
$this->sessions->updateCache($this);
} }
/** /**

View File

@@ -242,6 +242,18 @@ class Sessions
$this->store()->collectGarbage(); $this->store()->collectGarbage();
} }
/**
* Updates the instance cache with a newly created
* session or a session with a regenerated token
*
* @internal
* @param Kirby\Session\Session $session Session instance to push to the cache
*/
public function updateCache(Session $session)
{
$this->cache[$session->token()] = $session;
}
/** /**
* Returns the auth token from the cookie * Returns the auth token from the cookie
* *

View File

@@ -843,6 +843,26 @@ class Collection extends Iterator implements Countable
return $collection; return $collection;
} }
/**
* Get sort arguments from a string
*
* @param string $sortBy
* @return array
*/
public static function sortArgs(string $sortBy): array
{
$sortArgs = Str::split($sortBy, ' ');
// fill in PHP constants
array_walk($sortArgs, function (string &$value) {
if (Str::startsWith($value, 'SORT_') === true && defined($value) === true) {
$value = constant($value);
}
});
return $sortArgs;
}
/** /**
* Sorts the elements by any number of fields * Sorts the elements by any number of fields
* *

View File

@@ -76,6 +76,11 @@ class Query
*/ */
protected function resolve(string $query) protected function resolve(string $query)
{ {
// direct key access in arrays
if (is_array($this->data) === true && array_key_exists($query, $this->data) === true) {
return $this->data[$query];
}
$parts = $this->parts($query); $parts = $this->parts($query);
$data = $this->data; $data = $this->data;
$value = null; $value = null;

View File

@@ -826,7 +826,6 @@ class Str
return $string; return $string;
} }
$string = trim((string)$string, $separator);
$parts = explode($separator, $string); $parts = explode($separator, $string);
$out = []; $out = [];

View File

@@ -3,6 +3,7 @@
namespace Kirby\Toolkit; namespace Kirby\Toolkit;
use Exception; use Exception;
use Kirby\Http\Idn;
use Kirby\Toolkit\Str; use Kirby\Toolkit\Str;
use ReflectionFunction; use ReflectionFunction;
use Throwable; use Throwable;
@@ -270,7 +271,20 @@ V::$validators = [
* Checks for valid email addresses * Checks for valid email addresses
*/ */
'email' => function ($value): bool { 'email' => function ($value): bool {
return filter_var($value, FILTER_VALIDATE_EMAIL) !== false; if (filter_var($value, FILTER_VALIDATE_EMAIL) === false) {
try {
$parts = Str::split($value, '@');
$address = $parts[0] ?? null;
$domain = Idn::encode($parts[1] ?? '');
$email = $address . '@' . $domain;
} catch (Throwable $e) {
return false;
}
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
return true;
}, },
/** /**