feat(admin): Verwaltung fuer Benutzer, Gruppen und das eigene Konto
Setzt die Rechte aus dem vorigen Commit in den Oberflaechen um und
ergaenzt die fehlenden Verwaltungsseiten.
Neu:
- /admin/users - Konten anlegen, Rolle und Sperre setzen, Passwort
zuruecksetzen, Gruppenzuordnung je Konto
- /admin/groups - Gruppen anlegen und umbenennen, Mitglieder samt
Rolle pflegen, zugeordnete Domains einsehen
- /admin/profile - eigenes Konto: Gruppenzugehoerigkeit ansehen und
das Passwort aendern. Das fehlte bisher vollstaendig, auch fuer
den einen Administrator.
Domains sind jetzt Administratorsache, weil dort die Gruppe zugewiesen
wird. Kurzlinks, Statistik und QR-Codes richten sich nach der Gruppe:
ein fremder Kurzlink antwortet mit 404 (seine Existenz geht den
Aufrufer nichts an), ein sichtbarer ohne Schreibrecht mit 403.
Geschlossene Luecke: /admin/uploads/{file} pruefte bisher nur, ob
ueberhaupt jemand angemeldet ist. Da alle Logos in einem gemeinsamen
Verzeichnis liegen, war der Dateiname die einzige Huerde. Ausgeliefert
wird jetzt nur noch, was laut Datenbank zu einem Kurzlink aus einer
Gruppe des Aufrufers gehoert.
Die Navigation blendet aus, wofuer die Rechte fehlen - das ist
Bedienkomfort, kein Zugriffsschutz: die Routen pruefen unabhaengig
davon noch einmal.
Der Setup-Assistent legt das erste Konto als Administrator an und
traegt es gleich als Editor der Gruppe "Standard" ein. Ohne das
koennte es zwar verwalten, aber selbst keine Kurzlinks anlegen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ use App\Config;
|
||||
use App\Http\Request;
|
||||
use App\Http\Response;
|
||||
use App\Repository\DomainRepository;
|
||||
use App\Repository\GroupRepository;
|
||||
use App\Repository\SettingRepository;
|
||||
use App\Security\Auth;
|
||||
use App\Security\Csrf;
|
||||
@@ -19,6 +20,11 @@ use App\View;
|
||||
|
||||
/**
|
||||
* Verwaltung der Kurzlink-Domains.
|
||||
*
|
||||
* Ausschliesslich fuer Administratoren: eine Domain gehoert zu einer
|
||||
* Gruppe und entscheidet damit, wer die Kurzlinks darunter sehen und
|
||||
* anlegen darf. Einzige Ausnahme ist der Endpunkt der Domain-Pruefung,
|
||||
* der sich ueber ein kurzlebiges Token ausweist.
|
||||
*/
|
||||
final class DomainController extends Controller
|
||||
{
|
||||
@@ -31,13 +37,14 @@ final class DomainController extends Controller
|
||||
private readonly DomainRepository $domains,
|
||||
private readonly DomainChecker $checker,
|
||||
private readonly SettingRepository $settings,
|
||||
private readonly GroupRepository $groups,
|
||||
) {
|
||||
parent::__construct($config, $view);
|
||||
}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
if (($response = $this->requireAuth($this->auth, $this->session, $request)) !== null) {
|
||||
if (($response = $this->requireAdmin($this->auth, $this->session, $request)) !== null) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
@@ -50,6 +57,7 @@ final class DomainController extends Controller
|
||||
|
||||
return $this->html('domains/index', [
|
||||
'domains' => $this->domains->all(),
|
||||
'groups' => $this->groups->all(),
|
||||
'requestHost' => ShortDomain::normalizeRequestHost((string) ($request->server['HTTP_HOST'] ?? '')),
|
||||
'checkResult' => is_array($checkResult) ? $checkResult : null,
|
||||
'pageTitle' => 'Domains',
|
||||
@@ -63,7 +71,7 @@ final class DomainController extends Controller
|
||||
*/
|
||||
public function check(Request $request, array $args = []): Response
|
||||
{
|
||||
if (($response = $this->requireAuth($this->auth, $this->session, $request)) !== null) {
|
||||
if (($response = $this->requireAdmin($this->auth, $this->session, $request)) !== null) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
@@ -105,13 +113,19 @@ final class DomainController extends Controller
|
||||
|
||||
public function create(Request $request): Response
|
||||
{
|
||||
if (($response = $this->requireAuth($this->auth, $this->session, $request)) !== null) {
|
||||
if (($response = $this->requireAdmin($this->auth, $this->session, $request)) !== null) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
return $this->html('domains/form', [
|
||||
'domain' => null,
|
||||
'values' => ['base_url' => '', 'label' => '', 'is_default' => '0'],
|
||||
'groups' => $this->groups->all(),
|
||||
'values' => [
|
||||
'base_url' => '',
|
||||
'label' => '',
|
||||
'is_default' => '0',
|
||||
'group_id' => (string) $this->groups->defaultGroupId(),
|
||||
],
|
||||
'errors' => [],
|
||||
'pageTitle' => 'Neue Domain',
|
||||
]);
|
||||
@@ -119,7 +133,7 @@ final class DomainController extends Controller
|
||||
|
||||
public function store(Request $request): Response
|
||||
{
|
||||
if (($response = $this->requireAuth($this->auth, $this->session, $request)) !== null) {
|
||||
if (($response = $this->requireAdmin($this->auth, $this->session, $request)) !== null) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
@@ -135,9 +149,12 @@ final class DomainController extends Controller
|
||||
$errors['base_url'] = 'Diese Domain ist bereits eingetragen.';
|
||||
}
|
||||
|
||||
$groupId = $this->resolveGroupId($values['group_id'], $errors);
|
||||
|
||||
if ($errors !== []) {
|
||||
return $this->html('domains/form', [
|
||||
'domain' => null,
|
||||
'groups' => $this->groups->all(),
|
||||
'values' => $values,
|
||||
'errors' => $errors,
|
||||
'pageTitle' => 'Neue Domain',
|
||||
@@ -146,7 +163,7 @@ final class DomainController extends Controller
|
||||
|
||||
// Die erste Domain ist zwangslaeufig die Standarddomain.
|
||||
$isDefault = $values['is_default'] === '1' || $this->domains->count() === 0;
|
||||
$this->domains->create($baseUrl, $values['label'] !== '' ? $values['label'] : null, $isDefault);
|
||||
$this->domains->create($baseUrl, $values['label'] !== '' ? $values['label'] : null, $isDefault, $groupId);
|
||||
|
||||
$this->session->flash('success', sprintf(
|
||||
'Domain "%s" wurde angelegt. Bitte daran denken, sie im DNS und beim Webserver auf diese Installation zu zeigen.',
|
||||
@@ -161,7 +178,7 @@ final class DomainController extends Controller
|
||||
*/
|
||||
public function edit(Request $request, array $args = []): Response
|
||||
{
|
||||
if (($response = $this->requireAuth($this->auth, $this->session, $request)) !== null) {
|
||||
if (($response = $this->requireAdmin($this->auth, $this->session, $request)) !== null) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
@@ -172,10 +189,12 @@ final class DomainController extends Controller
|
||||
|
||||
return $this->html('domains/form', [
|
||||
'domain' => $domain,
|
||||
'groups' => $this->groups->all(),
|
||||
'values' => [
|
||||
'base_url' => (string) $domain['base_url'],
|
||||
'label' => (string) ($domain['label'] ?? ''),
|
||||
'is_default' => ((int) $domain['is_default'] === 1) ? '1' : '0',
|
||||
'group_id' => (string) $domain['group_id'],
|
||||
],
|
||||
'errors' => [],
|
||||
'pageTitle' => 'Domain bearbeiten',
|
||||
@@ -187,7 +206,7 @@ final class DomainController extends Controller
|
||||
*/
|
||||
public function update(Request $request, array $args = []): Response
|
||||
{
|
||||
if (($response = $this->requireAuth($this->auth, $this->session, $request)) !== null) {
|
||||
if (($response = $this->requireAdmin($this->auth, $this->session, $request)) !== null) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
@@ -209,16 +228,19 @@ final class DomainController extends Controller
|
||||
$errors['base_url'] = 'Diese Domain ist bereits eingetragen.';
|
||||
}
|
||||
|
||||
$groupId = $this->resolveGroupId($values['group_id'], $errors);
|
||||
|
||||
if ($errors !== []) {
|
||||
return $this->html('domains/form', [
|
||||
'domain' => $domain,
|
||||
'groups' => $this->groups->all(),
|
||||
'values' => $values,
|
||||
'errors' => $errors,
|
||||
'pageTitle' => 'Domain bearbeiten',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$this->domains->update($id, $baseUrl, $values['label'] !== '' ? $values['label'] : null);
|
||||
$this->domains->update($id, $baseUrl, $values['label'] !== '' ? $values['label'] : null, $groupId);
|
||||
|
||||
if ($values['is_default'] === '1') {
|
||||
$this->domains->makeDefault($id);
|
||||
@@ -234,7 +256,7 @@ final class DomainController extends Controller
|
||||
*/
|
||||
public function makeDefault(Request $request, array $args = []): Response
|
||||
{
|
||||
if (($response = $this->requireAuth($this->auth, $this->session, $request)) !== null) {
|
||||
if (($response = $this->requireAdmin($this->auth, $this->session, $request)) !== null) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
@@ -261,7 +283,7 @@ final class DomainController extends Controller
|
||||
*/
|
||||
public function destroy(Request $request, array $args = []): Response
|
||||
{
|
||||
if (($response = $this->requireAuth($this->auth, $this->session, $request)) !== null) {
|
||||
if (($response = $this->requireAdmin($this->auth, $this->session, $request)) !== null) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
@@ -314,7 +336,31 @@ final class DomainController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{base_url:string,label:string,is_default:string}
|
||||
* Prueft die gewaehlte Gruppe. Ohne Angabe gilt die Standardgruppe -
|
||||
* eine Domain ohne Gruppe waere sonst fuer niemanden ausser
|
||||
* Administratoren nutzbar.
|
||||
*
|
||||
* @param array<string,string> $errors
|
||||
*/
|
||||
private function resolveGroupId(string $value, array &$errors): int
|
||||
{
|
||||
if ($value === '') {
|
||||
return $this->groups->defaultGroupId();
|
||||
}
|
||||
|
||||
$group = $this->groups->find((int) $value);
|
||||
|
||||
if ($group === null) {
|
||||
$errors['group_id'] = 'Bitte eine gültige Gruppe auswählen.';
|
||||
|
||||
return $this->groups->defaultGroupId();
|
||||
}
|
||||
|
||||
return (int) $group['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{base_url:string,label:string,is_default:string,group_id:string}
|
||||
*/
|
||||
private function readValues(Request $request): array
|
||||
{
|
||||
@@ -322,6 +368,7 @@ final class DomainController extends Controller
|
||||
'base_url' => $request->post('base_url'),
|
||||
'label' => mb_substr($request->post('label'), 0, 255),
|
||||
'is_default' => $request->hasPost('is_default') ? '1' : '0',
|
||||
'group_id' => $request->post('group_id'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user