Entwurf: Mitarbeiterverwaltung im Userpanel (Projekt A)
Die Sitzverwaltung im Portal ist vollstaendig gebaut und erreicht die Nextcloud nie — resend() traegt seit jeher "Invite delivery is mocked for now.". Das Modul wird verkauft und tut nichts. Beim Nachsehen kam der groessere Fund: die Kunden-Nextcloud hat ueberhaupt keinen Mailversand eingerichtet. Keine Freigabe-Benachrichtigung, kein "Passwort vergessen", nichts. Das ist die Voraussetzung und wird zuerst gebaut — ueber eine EIGENE Absenderdomain (clupilot.cloud), damit der Ruf der Domain, ueber die Rechnungen und Sicherheitsmeldungen gehen, nicht am Mailaufkommen der Kunden haengt. Tragende Entscheidung: niemand kennt ein fremdes Passwort. Nextcloud erzeugt es selbst (user:add --generate-password --email) und mailt dem Mitarbeiter einen Link, an dem er sein eigenes setzt. Ein Wechselzwang beim ersten Login ist in Nextcloud fuer einen EINZELNEN Benutzer nicht moeglich — das steht so im Entwurf, statt es zu umschreiben. Anlegen und Einladen sind getrennt, wie gewuenscht. Entziehen sperrt und loescht nichts. Ratelimit 10 je Kunde und 3 je Sitz pro Stunde. Eine technische Unsicherheit ist benannt statt angenommen: ob Speicherplatz 0 B "nichts" oder "unbegrenzt" heisst, bekommt einen eigenen Nachweisschritt im Plan.claude/nice-moser-521659
parent
1886bf2076
commit
d257deae01
|
|
@ -0,0 +1,380 @@
|
|||
# Mitarbeiterverwaltung im Userpanel — Entwurf
|
||||
|
||||
**Stand:** 2026-08-03
|
||||
**Projekt A von dreien** (B = Frühwarnsystem, fertig; C = Postfächer in der Konsole, offen)
|
||||
|
||||
## Worum es geht
|
||||
|
||||
Der Inhaber soll aus dem Userpanel heraus Mitarbeiter anlegen, einladen und
|
||||
verwalten. Mitarbeiter melden sich in der **Nextcloud-Instanz** an, nicht im
|
||||
Portal — im Userpanel hat ein Mitarbeiter nichts zu suchen.
|
||||
|
||||
## Ausgangslage: es ist eine Attrappe
|
||||
|
||||
Die Sitzverwaltung im Portal ist vollständig gebaut — Tabelle, Rollen,
|
||||
Sitzplatzgrenze aus dem Vertrag, Bestätigungs-Modals nach R23, Sperren gegen
|
||||
das Entfernen des letzten Inhabers. Nur:
|
||||
|
||||
**Keine einzige dieser Aktionen erreicht je die Nextcloud.**
|
||||
|
||||
`app/Livewire/Users.php` schreibt Zeilen in `seats` und sonst nichts. Einladen
|
||||
legt keinen Benutzer an. Aussetzen sperrt niemanden aus. Entziehen löscht eine
|
||||
Zeile bei uns, während der Mitarbeiter weiterarbeitet. `resend()` trägt seit
|
||||
jeher den Kommentar `// Invite delivery is mocked for now.` — es wird nie eine
|
||||
Mail verschickt.
|
||||
|
||||
Das Modul wird verkauft. Es tut nichts.
|
||||
|
||||
### Der zweite Fund: die Kunden-Nextcloud kann gar nicht mailen
|
||||
|
||||
Weder die Bereitstellung noch `deploy/bootstrap/assets/docker-compose.yml`
|
||||
setzen je einen Mailversand im Gast. Damit verschickt die Nextcloud eines
|
||||
Kunden **überhaupt keine Mail** — keine Freigabe-Benachrichtigung, keine
|
||||
Aktivitätsmeldung, kein „Passwort vergessen". Stumm, ohne Fehlermeldung.
|
||||
|
||||
Das ist die Voraussetzung für alles Weitere und wird deshalb zuerst gebaut.
|
||||
|
||||
## Die tragende Entscheidung: niemand kennt ein fremdes Passwort
|
||||
|
||||
Der Mitarbeiter bekommt sein Passwort **von seiner Nextcloud**, nicht von uns:
|
||||
|
||||
```
|
||||
occ user:add --generate-password --email <mail> --display-name <name> <uid>
|
||||
```
|
||||
|
||||
Nextcloud erzeugt ein Passwort, das **niemand** zu sehen bekommt, und schickt
|
||||
dem Mitarbeiter eine Willkommensmail mit einem zeitlich begrenzten Link, an
|
||||
dem er sein eigenes setzt. Weder der Inhaber noch der Betreiber noch CluPilot
|
||||
kennen es je.
|
||||
|
||||
Damit erledigen sich drei Fragen auf einmal:
|
||||
|
||||
- **Passwort vergessen** — der Mitarbeiter benutzt „Passwort vergessen" in der
|
||||
Nextcloud; der Inhaber hat zusätzlich einen Knopf „Erneut einladen".
|
||||
- **Kurzzeitig gültig** — die Gültigkeit des Links regelt Nextcloud selbst.
|
||||
- **Wie gebe ich es weiter** — gar nicht. Es gibt nichts weiterzugeben.
|
||||
|
||||
Verworfen wurde: ein von CluPilot erzeugtes Einmalpasswort mit Wechselzwang
|
||||
beim ersten Login. **Nextcloud kann das nicht.** Eine Ablauffrist für
|
||||
Passwörter gibt es nur instanzweit (`password_policy`), sie träfe also den
|
||||
Inhaber und jeden anderen gleichermaßen. Einen Wechselzwang für einen
|
||||
einzelnen Benutzer gibt es nicht.
|
||||
|
||||
---
|
||||
|
||||
## Teil 1: Mailversand in der Kunden-Nextcloud
|
||||
|
||||
### Eigene Absenderdomain
|
||||
|
||||
Die Kunden-Nextcloud verschickt über **`clupilot.cloud`**, nicht über
|
||||
`clupilot.com`.
|
||||
|
||||
Der Grund ist Absenderruf: über `clupilot.com` laufen Rechnungen,
|
||||
Bestellbestätigungen und Sicherheitsmeldungen an die Kunden. Wenn die Nextcloud
|
||||
eines Kunden Freigabe-Benachrichtigungen am laufenden Band verschickt und
|
||||
dabei in Sperrlisten gerät, darf das die Domain nicht mitreißen, über die das
|
||||
Geschäft läuft. Zwei Domains, zwei Rufe, ein Ausfall bleibt einer.
|
||||
|
||||
### Was eingetragen wird
|
||||
|
||||
Ein neuer Bereitstellungsschritt schreibt in jeden Gast:
|
||||
|
||||
| Schlüssel | Wert |
|
||||
|---|---|
|
||||
| `mail_smtpmode` | `smtp` |
|
||||
| `mail_smtphost` | aus dem CluPilot-Postfach |
|
||||
| `mail_smtpport` | aus dem CluPilot-Postfach |
|
||||
| `mail_smtpsecure` | aus `App\Services\Mail\MailTlsPolicy` des Postfachs |
|
||||
| `mail_smtpauth` | `true` |
|
||||
| `mail_smtpname` | Benutzername des Postfachs |
|
||||
| `mail_smtppassword` | Passwort des Postfachs |
|
||||
| `mail_from_address` | `noreply` |
|
||||
| `mail_domain` | `clupilot.cloud` |
|
||||
| `mail_sendmailmode` | entfällt |
|
||||
|
||||
Der Anzeigename ist der Cloud-Name des Kunden, damit eine Mail aus *seiner*
|
||||
Cloud kommt und nicht aus einem Produkt, das er nicht kennt.
|
||||
|
||||
### Das Passwort darf nicht in die Prozessliste
|
||||
|
||||
`occ config:system:set mail_smtppassword --value=…` würde das Passwort als
|
||||
Befehlszeilen-Argument in den Gast tragen. Jeder, der dort `ps` ausführen kann,
|
||||
liest es mit — und das ist auf einer Kundenmaschine niemand, dem wir das
|
||||
SMTP-Passwort unserer Versandinfrastruktur anvertrauen wollen.
|
||||
|
||||
Deshalb geht die ganze Mailkonfiguration als **JSON über die Standardeingabe**
|
||||
hinein:
|
||||
|
||||
```
|
||||
occ config:import < /dev/stdin
|
||||
```
|
||||
|
||||
Die Zeile wird in `App\Support\NextcloudOcc` gebaut, wie jeder andere
|
||||
occ-Aufruf auch. Nichts in `app/` schreibt `docker compose exec` selbst — das
|
||||
ist eine bestehende, testerzwungene Regel dieses Projekts.
|
||||
|
||||
### Bestand nachrüsten
|
||||
|
||||
Ein Artisan-Befehl `clupilot:configure-instance-mail` fährt denselben Schritt
|
||||
über alle aktiven Instanzen. Niemand muss neu bereitgestellt werden. Der
|
||||
Befehl ist wiederholbar: dieselben Werte zweimal zu schreiben ändert nichts.
|
||||
|
||||
### Neues CluPilot-Postfach
|
||||
|
||||
Ein `Mailbox`-Datensatz mit dem Schlüssel `instance-relay` und der Adresse
|
||||
`noreply@clupilot.cloud`. Er erscheint in der Postfachverwaltung der Konsole
|
||||
wie die anderen auch.
|
||||
|
||||
**Nicht** in `MailCatalogue`: die Einladungsmail ist keine CluPilot-Mail mehr.
|
||||
Sie entsteht in der Nextcloud des Kunden. Auf der Seite „Wegwahl je Mailart"
|
||||
taucht sie deshalb bewusst nicht auf — was dort steht, verschickt CluPilot
|
||||
selbst.
|
||||
|
||||
---
|
||||
|
||||
## Teil 2: Anlegen ist nicht Einladen
|
||||
|
||||
Zwei getrennte Vorgänge, ausdrücklich so gewünscht:
|
||||
|
||||
| Vorgang | Wirkung bei uns | Wirkung in der Nextcloud |
|
||||
|---|---|---|
|
||||
| **Anlegen** | Sitz mit Name, Mail, Rolle | keine |
|
||||
| **Einladen** | Zustand → „wird eingerichtet" | `user:add --generate-password --email` |
|
||||
| **Erneut einladen** | Zustand bleibt | `user:welcome --reset-password` |
|
||||
|
||||
Ein Inhaber kann also sein Team vorbereiten und die Einladungen später
|
||||
verschicken — etwa alle am ersten Arbeitstag.
|
||||
|
||||
### Der Nextcloud-Benutzername
|
||||
|
||||
`nc_username` wird beim **ersten** Einladen gesetzt und danach **nie wieder
|
||||
geändert**. Nextcloud kann Benutzer nicht umbenennen; ein Sitz, dessen
|
||||
Mailadresse sich später ändert, behält seinen Anmeldenamen.
|
||||
|
||||
Als Benutzername dient die Mailadresse. Nextcloud lässt `@`, `.`, `-` und `_`
|
||||
in Benutzerkennungen zu, und die Adresse ist innerhalb eines Kunden ohnehin
|
||||
eindeutig — das erzwingt die bestehende Prüfung in `Users::invite()`.
|
||||
|
||||
### Der Inhaber-Sitz ist der bestehende Admin
|
||||
|
||||
Der Sitz mit der Rolle `owner` wird **nicht** neu angelegt. Sein
|
||||
`nc_username` ist `Instance::nc_admin_ref` — das Konto, das die Bereitstellung
|
||||
in `CreateCustomerAdmin` erzeugt hat. Ein zweites Admin-Konto für dieselbe
|
||||
Person wäre ein Konto zu viel.
|
||||
|
||||
---
|
||||
|
||||
## Teil 3: Rollen sind Nextcloud-Gruppen
|
||||
|
||||
Vier Rollen bleiben. Jede ist eine Gruppe im Gast, damit weitere später eine
|
||||
Zeile sind und kein Umbau.
|
||||
|
||||
| Rolle | Nextcloud-Gruppe | Was sie bewirkt |
|
||||
|---|---|---|
|
||||
| `owner` | `admin` | Vollzugriff; ist das bestehende Admin-Konto |
|
||||
| `admin` | `admin` | Vollzugriff, kann Benutzer verwalten |
|
||||
| `member` | `mitarbeiter` | normaler Benutzer |
|
||||
| `readonly` | `nur-lesen` | Speicherplatz `0 B` — kann nichts hochladen |
|
||||
|
||||
Rollenwechsel: `group:adduser` und `group:removeuser`. Wird `readonly`
|
||||
**gesetzt**, geht der Speicherplatz auf `0 B`; wird sie **verlassen**, geht er
|
||||
auf `default` zurück — nicht auf einen festen Wert, denn die Vorgabe der
|
||||
Instanz hängt am gekauften Paket (`ApplyStorageQuota` setzt sie).
|
||||
|
||||
### Was `readonly` ehrlicherweise NICHT ist
|
||||
|
||||
Nextcloud kennt keine Rolle „nur lesen". Ein Speicherplatz von `0 B`
|
||||
verhindert das **Hochladen und Anlegen** — mehr nicht. Was jemandem mit
|
||||
Bearbeitungsrecht freigegeben wurde, kann er weiterhin ändern und löschen.
|
||||
|
||||
Die Oberfläche muss das genau so sagen: **„Kann nichts hochladen oder anlegen.
|
||||
Was ihm freigegeben wird, kann er im Rahmen der Freigabe bearbeiten."** Eine
|
||||
Rolle, die mehr verspricht als sie hält, ist schlimmer als keine.
|
||||
|
||||
### Offene technische Unsicherheit
|
||||
|
||||
Ob Nextcloud den Speicherplatzwert `0 B` als „null Bytes" und nicht als
|
||||
„unbegrenzt" auslegt, ist aus der Dokumentation nicht zu belegen. Das ist
|
||||
**kein Annahmerecht**: der Umsetzungsplan bekommt einen eigenen Nachweisschritt
|
||||
gegen eine echte Instanz, bevor `readonly` als fertig gilt. Fällt der Nachweis
|
||||
negativ aus, ist die Rückfallebene die Gruppe ohne Speicherplatzgrenze plus
|
||||
ein deutlicher Hinweis in der Oberfläche, dass die Beschränkung über
|
||||
Freigaberechte läuft.
|
||||
|
||||
---
|
||||
|
||||
## Teil 4: Aussetzen, reaktivieren, entziehen
|
||||
|
||||
| Knopf | Nextcloud |
|
||||
|---|---|
|
||||
| Aussetzen | `user:disable` **und** `user:auth-tokens:delete` |
|
||||
| Reaktivieren | `user:enable` |
|
||||
| Entziehen | `user:disable`, Sitz auf `revoked` |
|
||||
|
||||
`user:disable` allein beendet laufende Sitzungen erst binnen fünf Minuten.
|
||||
`user:auth-tokens:delete` wirft sie sofort hinaus — bei einem Mitarbeiter, der
|
||||
gerade gegangen ist, sind fünf Minuten fünf zu viel.
|
||||
|
||||
### Entziehen löscht nichts
|
||||
|
||||
**Kein Nextcloud-Benutzer wird je gelöscht, und keine Datei.** Entziehen ist
|
||||
Sperren mit einem anderen Namen. Der Sitz bleibt als Zeile stehen, der Zugang
|
||||
ist zu, die Arbeit des Mitarbeiters bleibt dort, wo sein Team sie braucht.
|
||||
|
||||
Das ist ausdrücklich entschieden und nicht bloß der bequemere Weg: ein Fehlgriff
|
||||
im Userpanel darf nicht die Arbeit eines Menschen vernichten. Wer wirklich
|
||||
löschen will, tut das in der Nextcloud selbst, wo Nextcloud danach fragt, was
|
||||
mit den Dateien geschehen soll.
|
||||
|
||||
Der bestehende `revoke()`, der die Zeile mit `$seat->delete()` entfernt, wird
|
||||
entsprechend umgebaut. Die Sitzplatzzählung (`usedSeats()`) zählt schon heute
|
||||
`status != 'revoked'`, passt also unverändert.
|
||||
|
||||
---
|
||||
|
||||
## Teil 5: Ratelimit
|
||||
|
||||
Zwei Grenzen über Laravels `RateLimiter`:
|
||||
|
||||
- **10 Zugangsmails je Kunde und Stunde**
|
||||
- **3 je Sitz und Stunde**
|
||||
|
||||
Betroffen sind „Einladen" und „Erneut einladen" — beides löst eine Mail aus.
|
||||
|
||||
Wer dagegenläuft, bekommt **„Zu viele Einladungen — in X Minuten wieder
|
||||
möglich"** zu lesen, mit der echten Restzeit. Eine stumme Verweigerung wäre
|
||||
schlimmer als die Grenze selbst.
|
||||
|
||||
---
|
||||
|
||||
## Teil 6: Der Sitz zeigt, was wirklich ist
|
||||
|
||||
Jede Nextcloud-Änderung läuft über die **Bereitstellungs-Warteschlange** —
|
||||
nur dieser Arbeiter hat den Tunnel und die Proxmox-Zugangsdaten. Die Seite
|
||||
selbst erreicht keinen Gast, und sie darf es auch nicht versuchen.
|
||||
|
||||
Daraus folgt: **der Knopf wirkt nicht sofort.** Ohne sichtbaren Zustand drückt
|
||||
der Inhaber wieder und wieder, weil nichts passiert — genau die Beschwerde,
|
||||
die dieses Projekt schon zweimal hatte.
|
||||
|
||||
Deshalb führt der Sitz zwei Angaben nebeneinander:
|
||||
|
||||
- **`status`** — was der Inhaber WILL (`invited`, `active`, `suspended`,
|
||||
`revoked`). Ändert sich sofort beim Klick.
|
||||
- **`nc_state`** — was in der Nextcloud WIRKLICH ist (`none`, `pending`,
|
||||
`synced`, `failed`). Ändert sich, wenn der Auftrag durch ist.
|
||||
|
||||
Absicht und Wirklichkeit getrennt zu führen ist der einzige Weg, „fehlgeschlagen"
|
||||
überhaupt sagen zu können. Ein einzelnes Feld müsste lügen.
|
||||
|
||||
Dazu `nc_error` (Klartextgrund) und `nc_synced_at`.
|
||||
|
||||
### In der Oberfläche
|
||||
|
||||
| `nc_state` | Anzeige |
|
||||
|---|---|
|
||||
| `none` | „angelegt — noch nicht eingeladen" |
|
||||
| `pending` | „wird eingerichtet …" |
|
||||
| `synced` | der `status` selbst (aktiv / gesperrt / entzogen) |
|
||||
| `failed` | **„fehlgeschlagen"** mit Grund und Knopf „Nochmal versuchen" |
|
||||
|
||||
---
|
||||
|
||||
## Fehlerbehandlung
|
||||
|
||||
Der Auftrag schlägt fehl, wenn die Instanz nicht `active` ist, kein Host
|
||||
hängt, `vmid` fehlt, der Gastagent nicht antwortet oder occ einen Fehlercode
|
||||
liefert. In jedem Fall:
|
||||
|
||||
1. `nc_state = failed`, `nc_error` = ein Grund in Klartext, kein Stacktrace.
|
||||
2. `Log::warning` mit Sitz-UUID und Instanz-UUID, **nie** mit Zugangsdaten.
|
||||
3. Der Auftrag wirft nicht weiter — ein hängender Gast darf den
|
||||
Bereitstellungs-Arbeiter nicht mitreißen, auf dem die bezahlte
|
||||
Kundenbereitstellung läuft.
|
||||
|
||||
`tries = 1`, wie bei `IssueInstanceAdminAccess`: ein blinder Wiederholungslauf
|
||||
gegen einen nicht erreichbaren Gast bringt nichts, und der Inhaber hat einen
|
||||
Knopf dafür.
|
||||
|
||||
### Wiederholbarkeit
|
||||
|
||||
Einladen prüft zuerst mit `user:info`, ob der Benutzer schon existiert — genau
|
||||
wie `CreateCustomerAdmin` es tut. Ein zweiter Lauf nach einem Absturz legt
|
||||
keinen zweiten Benutzer an, sondern schickt die Willkommensmail erneut.
|
||||
|
||||
---
|
||||
|
||||
## Datenmodell
|
||||
|
||||
Wanderung an `seats`:
|
||||
|
||||
| Spalte | Typ | Zweck |
|
||||
|---|---|---|
|
||||
| `nc_username` | `string`, nullable | einmalig gesetzt, nie geändert |
|
||||
| `nc_state` | `string`, Vorgabe `none` | `none`/`pending`/`synced`/`failed` |
|
||||
| `nc_error` | `text`, nullable | Klartextgrund des letzten Fehlschlags |
|
||||
| `nc_synced_at` | `timestamp`, nullable | wann zuletzt erfolgreich |
|
||||
|
||||
Bestehende Sitze bekommen `nc_state = 'none'`. Sie sind damit ehrlich
|
||||
beschrieben: angelegt, nie eingeladen — was der Wahrheit entspricht, denn
|
||||
eingeladen hat sie nie jemand.
|
||||
|
||||
**Eine Ausnahme, und sie ist wichtig:** der `owner`-Sitz eines Kunden, dessen
|
||||
Instanz ein `nc_admin_ref` trägt, bekommt in derselben Wanderung
|
||||
`nc_username = nc_admin_ref` und `nc_state = 'synced'`. Dieser Zugang existiert
|
||||
in der Nextcloud tatsächlich — die Bereitstellung hat ihn angelegt. Stünde er
|
||||
auf `none`, böte das Userpanel dem Inhaber an, sich selbst einzuladen, und der
|
||||
Auftrag träfe auf einen Benutzer, den es längst gibt.
|
||||
|
||||
---
|
||||
|
||||
## Sicherheitsgrenzen
|
||||
|
||||
- **Jede Aktion prüft, dass der Sitz zum angemeldeten Kunden gehört** — auf der
|
||||
Seite UND im Auftrag. Ein Modal ist ohne die Middleware der Seite erreichbar
|
||||
(R20); dieselbe Regel, die schon beim Frühwarnsystem galt.
|
||||
- **Der Auftrag prüft erneut**, dass die Instanz aktiv ist und dem Kunden
|
||||
gehört. Eine geschlossene Instanz kann ihre VMID auf demselben Host
|
||||
weiterverliehen haben — dieselbe Falle, die in
|
||||
`IssueInstanceAdminAccess` schon beschrieben steht.
|
||||
- **Der letzte Inhaber bleibt.** Die bestehenden Sperren gegen das Entfernen
|
||||
oder Herabstufen des letzten `owner` bleiben unverändert.
|
||||
- **Ein Inhaber kann sich nicht selbst aussperren.** Die bestehende Prüfung in
|
||||
`suspend()` bleibt.
|
||||
- **Keine Zugangsdaten ins Log**, in keiner Form.
|
||||
- **Bestätigung im Modal** (R23) für Aussetzen und Entziehen, kein
|
||||
Browser-Dialog.
|
||||
|
||||
---
|
||||
|
||||
## Prüfung
|
||||
|
||||
- **Einheit:** Befehlsbau in `NextcloudOcc` — dass das SMTP-Passwort in
|
||||
**keinem** erzeugten Befehl als Argument steht. Strukturell geprüft, am
|
||||
erzeugten Befehl, nicht am Quelltext.
|
||||
- **Aufträge:** gegen `FakeProxmoxClient` — angelegt, eingeladen, gesperrt,
|
||||
reaktiviert, Rolle gewechselt; und für jeden Zweig der Fehlschlag mit
|
||||
`nc_state = failed`.
|
||||
- **Wiederholbarkeit:** zweimal einladen legt keinen zweiten Benutzer an.
|
||||
- **Seite:** ein fremder Sitz lässt sich nicht anfassen (403); die Zeile
|
||||
erneuert sich nach jeder Aktion — die Prüfung über den **ganzen** Weg,
|
||||
Modal-Ereignis eingeschlossen, nicht nur der direkte Methodenaufruf.
|
||||
- **Ratelimit:** die elfte Einladung derselben Stunde wird abgewiesen, mit
|
||||
Restzeit in der Meldung.
|
||||
- **Löschverbot:** kein Auftrag im Repo darf `user:delete` enthalten —
|
||||
testerzwungen, wie die anderen Regeln dieses Projekts.
|
||||
- **Nachweis gegen echte Hardware:** Speicherplatz `0 B` verhindert das
|
||||
Hochladen. Eigener Schritt im Plan.
|
||||
|
||||
---
|
||||
|
||||
## Was nicht gebaut wird
|
||||
|
||||
- **Slack-Anbindung.** Eigenes Projekt, kein Nebensatz hier.
|
||||
- **Die `.cloud`-Domain als Marketing-Auftritt.** Für diesen Bau genügt sie als
|
||||
Absenderdomain.
|
||||
- **Endgültiges Löschen von Benutzern.** Siehe Teil 4.
|
||||
- **Selbst erstellte Rollen.** Die vier genügen für den Anfang; die
|
||||
Gruppen-Abbildung ist so gebaut, dass weitere später hinzukommen können.
|
||||
- **Ein Portalkonto für Mitarbeiter.** Sie melden sich in der Nextcloud an.
|
||||
- **Instanzweiter Passwortablauf.** Er träfe den Inhaber mit.
|
||||
Loading…
Reference in New Issue