# barox Inventory

Interne Webanwendung der barox Kommunikation AG zur Verwaltung von
IT-Hardware, Labor- und Testhardware, Zubehör, Softwarelizenzen sowie
Benutzer- und Systemberechtigungen (Netzlaufwerke, Funktionspostfächer,
Anwendungen). Ersetzt die bisherige SharePoint-Inventarliste und ergänzt
Hardware-Lebenszyklen, Budgetplanung und E-Mail-Benachrichtigungen.

- **Produktive URL:** https://inventory.barox.io
- **Anwendungspfad:** `/var/www/html/barox-inventory`
- **Stack:** Laravel 13, PHP ≥ 8.2 (produktiv 8.5), MySQL 8 / MariaDB, Apache, Blade + Alpine.js (lokal, kein CDN)
- **Anmeldung:** ausschließlich Microsoft 365 / Entra ID (OpenID Connect, Single Tenant) – keine lokalen Passwörter

---

## 1. Systemvoraussetzungen

| Komponente | Version |
|---|---|
| Ubuntu Server | 22.04+ |
| Apache | 2.4 mit `mod_rewrite` |
| PHP | ≥ 8.2 (getestet mit 8.5) |
| MySQL | 8.x (oder MariaDB 10.6+) |
| Composer | 2.x |

**Benötigte PHP-Erweiterungen:**
`pdo_mysql`, `mbstring`, `xml`, `dom`, `curl`, `openssl`, `ctype`, `fileinfo`,
`tokenizer`, `json`, `bcmath`, `zip`, `intl` (für Tests zusätzlich `pdo_sqlite`).

Prüfen mit: `php -m`

## 2. Installation

```bash
# 1. Quellcode nach /var/www/html/barox-inventory bringen (Git, rsync, SFTP)
cd /var/www/html/barox-inventory

# 2. Abhängigkeiten installieren
composer install --no-dev --optimize-autoloader

# 3. Umgebungsdatei anlegen
cp .env.example .env
php artisan key:generate
nano .env        # DB-, Azure- und Mail-Werte eintragen (siehe unten)

# 4. Datenbank anlegen
mysql -u root -p -e "CREATE DATABASE barox_inventory CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql -u root -p -e "GRANT ALL ON barox_inventory.* TO 'barox_inv'@'localhost' IDENTIFIED BY '<sicheres-passwort>';"

# 5. Migrationen und Standarddaten (Kategorien)
php artisan migrate --force
php artisan db:seed --force

# 6. Storage-Link (für evtl. öffentliche Dateien; Anhänge liegen bewusst NICHT hier)
php artisan storage:link

# 7. Caches aufbauen
php artisan config:cache
php artisan route:cache
php artisan view:cache
```

### Dateiberechtigungen

```bash
sudo chown -R <deploy-user>:www-data /var/www/html/barox-inventory
sudo chmod -R 775 storage bootstrap/cache
```

`storage/` enthält Logs, Sessions-Fallback, hochgeladene **Anhänge**
(`storage/app/private/attachments`) und temporäre Importdateien
(`storage/app/private/imports/tmp`, werden nach dem Import gelöscht).
Diese Pfade liegen außerhalb des DocumentRoot und sind nur über
berechtigungsgeprüfte Laravel-Routen erreichbar.

## 3. Entra-ID-App-Registrierung (Microsoft 365 Login)

1. https://entra.microsoft.com → **App registrations** → **New registration**
2. Name: `barox Inventory`
3. **Supported account types:** *Accounts in this organizational directory only* (Single Tenant)
4. **Redirect URI** (Typ *Web*): `https://inventory.barox.io/auth/microsoft/callback`
5. Nach dem Anlegen:
   - **Overview** → `Application (client) ID` → `.env` `AZURE_CLIENT_ID`
   - **Overview** → `Directory (tenant) ID` → `.env` `AZURE_TENANT_ID`
   - **Certificates & secrets** → **New client secret** → Wert in `.env` `AZURE_CLIENT_SECRET`
     (Ablaufdatum notieren und rechtzeitig erneuern!)
6. **API permissions:** `Microsoft Graph → Delegated → openid, profile, email`
   (Standard bei OpenID Connect; keine weiteren Berechtigungen nötig, kein Admin Consent erforderlich)
7. **Authentication:** *ID tokens* muss nicht aktiviert werden (Authorization Code Flow mit PKCE).

Die Anwendung prüft serverseitig `tid` (Tenant), `aud` (Client-ID), `iss` und `exp`
des ID-Tokens; nur Benutzer des konfigurierten Tenants können sich anmelden.
Benutzer werden beim ersten erfolgreichen Login automatisch angelegt und anhand
der stabilen **Entra Object ID** identifiziert. Das Abmelden beendet die lokale
Session und leitet zum Microsoft-Logout weiter.

### Erster Administrator

In `.env` die kommagetrennte Liste setzen, **bevor** sich die Person anmeldet:

```env
ADMIN_EMAILS=it@barox.ch,zweite.person@barox.ch
```

Beim Login wird die Person automatisch Systemadministrator. Weitere Administratoren
können danach in der Oberfläche (*Mehr → Benutzerverwaltung*) ernannt werden.
Dort lassen sich Benutzer auch **sperren** (gesperrte Benutzer können sich nicht anmelden).

## 4. Apache-Konfiguration

Vorlage: [`deploy/apache-inventory.barox.io.conf`](deploy/apache-inventory.barox.io.conf)

```bash
sudo cp deploy/apache-inventory.barox.io.conf /etc/apache2/sites-available/inventory.barox.conf
sudo a2enmod rewrite headers
sudo a2ensite inventory.barox
sudo systemctl reload apache2
```

**Wichtig:** Der `DocumentRoot` muss auf
`/var/www/html/barox-inventory/public` zeigen – niemals auf das
Projektverzeichnis selbst, sonst wären `.env` und Quellcode öffentlich.
> Hinweis: Auf dem Zielserver existierte bereits ein `inventory.barox.conf`
> mit DocumentRoot auf dem Projektordner. Dieser muss auf `…/public`
> geändert werden (Root-Rechte erforderlich).

TLS (https://inventory.barox.io) gemäß auskommentiertem 443-Block in der
Vorlage bzw. vorhandener firmeninterner Zertifikatslösung einrichten.
`SESSION_SECURE_COOKIE=true` setzt sichere Cookies voraus (HTTPS).

## 5. Mail-Konfiguration

Versand per SMTP (Firmenkonvention: `mail.netzone.ch:587`, STARTTLS,
eigenes Postfach je Anwendung, z. B. `inventory@barox.io`):

```env
MAIL_MAILER=smtp
MAIL_HOST=mail.netzone.ch
MAIL_PORT=587
MAIL_USERNAME=inventory@barox.io
MAIL_PASSWORD=<postfach-passwort>
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=inventory@barox.io
```

Solange kein Postfach existiert, bleibt `MAIL_MAILER=log` – E-Mails landen
dann in `storage/logs/laravel.log` (Annahme/Standardeinstellung dieser
Installation). Optional kann später Microsoft Graph als Versandweg ergänzt
werden; sämtliche Zugangsdaten kommen ausschließlich aus `.env`.

## 6. Cron (tägliche Benachrichtigungen)

Vorlage: [`deploy/crontab.example`](deploy/crontab.example)

```cron
CRON_TZ=Europe/Zurich
0 7 * * * cd /var/www/html/barox-inventory && php artisan inventory:send-notifications >> storage/logs/notifications.log 2>&1
```

Der Command sammelt je Inventarbereich alle fälligen Hinweise
(Hardware-Ersatz nach Lebenszyklus, Garantieabläufe, Lizenzablauf/-verlängerung,
Lizenz-Überbelegung) und versendet **eine zusammengefasste E-Mail** je Bereich.
Eine Benachrichtigungshistorie (`notification_logs`) verhindert doppelte Warnungen.

Test ohne Versand: `php artisan inventory:send-notifications --dry-run`
Stichtag simulieren: `php artisan inventory:send-notifications --date=2027-03-01 --dry-run`

## 7. Queue

Standardmäßig `QUEUE_CONNECTION=sync` – es wird kein Worker benötigt.
Bei größeren Datenmengen kann auf `database` umgestellt werden
(Tabellen existieren bereits); dann zusätzlich einen Worker einrichten:
`php artisan queue:work --daemon` (z. B. via systemd).

## 8. Funktionsüberblick

- **Inventarbereiche** mit Rollen je Bereich: **Besitzer** (alles inkl. Mitglieder,
  Löschen, Besitzübertragung), **Bearbeiter** (Inhalte pflegen, Import nur mit
  Freigabe), **Betrachter** (lesen, filtern, exportieren). Global: **Systemadministrator**.
  Alle Berechtigungen werden serverseitig per Policy geprüft (kein reines UI-Ausblenden).
- **Hardware/Assets** mit automatischer Asset-ID (`INV-000123`), Kategorien mit
  konfigurierbarer Standardlebensdauer, Zuweisungs-/Ausleih-/Umzugshistorie,
  Anhängen (Rechnung, Lieferschein, Garantie, Foto, Konfiguration) im geschützten
  Storage, Änderungsverlauf, Datenqualitätshinweisen.
- **Lebenszyklus:** Ersatzdatum = manuell oder `Inbetriebnahme + Lebensdauer`
  (Asset-Wert > Kategorie-Standard > Inventar-Standard). Dashboard-Vorschauen
  12/24 Monate, Budgetplanung nach Kalenderjahr, konfigurierbare
  Benachrichtigungszeitpunkte (24/12/6/3/1 Monate, am Datum, nach Überschreitung;
  Standard: 12 Monate, 3 Monate, am Datum).
- **Lizenzen** inkl. Seat-Verwaltung (gekauft/zugewiesen/frei/überbelegt),
  Zuweisungen an Personen oder Geräte, Ablauf-/Verlängerungswarnungen.
- **Berechtigungen:** Katalog je Inventarbereich (Netzlaufwerke, Anwendungen,
  Funktionspostfächer, Adminrollen, Fernzugriff), Zuweisungen mit Status,
  Gültigkeit, Genehmiger; Berechtigungsmatrix und Exporte.
- **CSV-Import-Assistent** (6 Schritte) für SharePoint-Exporte: erkennt UTF-8-BOM
  und `ListSchema=`-Metadatenblock (Kopfzeile darf ohne Zeilenumbruch am Block
  kleben), Komma/Anführungszeichen/mehrzeilige Felder, deutsche und ISO-Datumswerte,
  `Wahr/Falsch`, `x/X/1/true/ja`. Duplikat- und Datumsvalidierung, Import in einer
  Transaktion, Ergebnisstatistik + herunterladbares Protokoll, Importhistorie mit
  Dateihash. **Die Spalte `Initialkennwort` wird immer ignoriert, maskiert und
  niemals gespeichert oder protokolliert.**
- **Exporte** (CSV, UTF-8 mit BOM für Excel): Hardware gesamt/je Person/je Standort,
  Ausmusterungen, Ersatz-/Budgetplanung, Lizenzübersicht und -zuweisungen,
  Berechtigungsmatrix je Person und je Ressource.
- **Audit-Log** für Logins (auch fehlgeschlagen), CRUD, Zuweisungen, Statuswechsel,
  Rollenänderungen, Import/Export, Benachrichtigungen – mit gefilterten Differenzen
  (niemals Kennwörter/Secrets) und IP-Adresse.

## 9. Sicherheit

- Nur Microsoft-365-Login (Single Tenant, PKCE, State-Prüfung, Tenant-/Audience-/Issuer-/Ablauf-Validierung)
- Session-Cookies: `Secure`, `HttpOnly`, `SameSite=lax`; Session-Rotation beim Login
- CSRF-Schutz (Laravel), XSS-Schutz (Blade-Escaping), Eloquent/Prepared Statements
- Content-Security-Policy + Security-Header (Middleware `SecurityHeaders`),
  alle Assets lokal (Fonts, Alpine.js – keine CDNs)
- IDOR-Schutz: jede Route autorisiert am Inventarbereich des Objekts
- Datei-Uploads: Größen-/Typbeschränkung, Speicherung außerhalb des DocumentRoot,
  Downloads nur mit Berechtigungsprüfung; Rate Limiting auf Login und Uploads
- Soft Deletes für Inventare, Personen, Assets, Lizenzen, Ressourcen
- `APP_DEBUG=false` in Produktion → keine Stacktraces; eigene Fehlerseiten (403/404/419/429/500/503)
- Secrets ausschließlich in `.env` (nicht im Repository); Audit-Log filtert sensible Schlüssel

## 10. Tests

Tests nutzen SQLite (in-memory) und ausschließlich anonymisierte Daten.

```bash
composer install          # inkl. dev-Abhängigkeiten
php artisan test
```

Abgedeckt: Microsoft-Login-Callback (inkl. Tenant-/Audience-/State-/Ablauf-Prüfung,
Sperrung, ADMIN_EMAILS), Rollen & Inventartrennung & IDOR, Asset-Workflows
(Anlage, Zuweisung, Rückgabe, Historie), Lebenszyklusberechnung,
Benachrichtigungszeitpunkte & Dedupe, Lizenzplatzberechnung, CSV-Parser
(BOM, ListSchema, mehrzeilige Felder, Datumsformate, Booleans), Import-Assistent
(doppelte Personen, leere Mitarbeiter, Berechtigungs-/Lizenzimport,
Equipment-Zerlegung, Duplikate, **Initialkennwort-Sperre**), Upload-/Download-Berechtigungen.

## 11. Backup & Restore

- **Datenbank:** täglich `mysqldump --single-transaction barox_inventory > backup.sql`
- **Dateien:** `storage/app/private` (Anhänge) und `.env` sichern
- Restore: Datenbank einspielen, Dateien zurückkopieren, `php artisan config:cache`

Empfehlung: bestehende Backup-Routine des Servers um diese zwei Pfade ergänzen.

## 12. Update / Deployment

```bash
cd /var/www/html/barox-inventory
php artisan down
# Code aktualisieren (git pull / rsync)
composer install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan config:cache && php artisan route:cache && php artisan view:cache
php artisan up
```

**Deployment-Checkliste**
1. `.env` vollständig (APP_KEY, DB, AZURE_*, ADMIN_EMAILS, MAIL_*)?
2. DocumentRoot zeigt auf `public/`?
3. HTTPS aktiv (Redirect-URI in Entra stimmt exakt)?
4. `storage/` + `bootstrap/cache` beschreibbar (www-data)?
5. Cron-Eintrag installiert (`crontab -l`)?
6. Testlauf `php artisan inventory:send-notifications --dry-run`?
7. Login mit ADMIN_EMAILS-Konto erfolgreich, Benutzer erscheint in der Benutzerverwaltung?
8. Backup eingerichtet?

## 13. Fehlerbehebung

| Problem | Ursache/Lösung |
|---|---|
| Nach Login „falscher Tenant“ | `AZURE_TENANT_ID` prüft nicht den Tenant der Anmeldung – Tenant-ID in `.env` kontrollieren |
| `AADSTS50011` Redirect-URI | URI in Entra muss **exakt** `https://inventory.barox.io/auth/microsoft/callback` sein |
| 419 beim Absenden | Session-/CSRF-Ablauf → Seite neu laden; `SESSION_SECURE_COOKIE=true` erfordert HTTPS |
| Weiße Seite / 500 | `storage/logs/laravel.log` prüfen; `php artisan config:clear` |
| Schriftart falsch | `.otf`-Dateien unter `public/assets/fonts/` vorhanden? Sonst greift der System-Fallback |
| Keine Benachrichtigungen | Cron aktiv? `MAIL_MAILER` konfiguriert? `--dry-run` zeigt fällige Hinweise |
| Import bricht ab | Validierungsschritt zeigt Zeilenfehler; Import ist transaktional – es bleiben keine halben Datensätze zurück |
| „Forbidden“ bei /assets | Statische Dateien liegen unter `/assets`; die Hardware-Liste läuft deshalb unter `/hardware` |
| Fehler beim Datei-Upload | `storage/app` ist für `www-data` nicht zugänglich: `chgrp -R www-data storage bootstrap/cache && chmod -R g+rwX storage bootstrap/cache && find storage -type d -exec chmod g+s {} +` (g+rwX ist wichtig – nur g+w reicht nicht, Verzeichnisse brauchen auch Execute) |

## 14. Getroffene Annahmen

- Asset-IDs im Format `INV-000123` (fortlaufend, global eindeutig).
- Kategorien sind global (systemweit) und werden von Administratoren gepflegt;
  Standardwerte gemäß Vorgabe geseedet (Notebook/Desktop/Server 5 J., Smartphone 4 J.,
  Monitor/Netzwerk/Labor 7 J., Sonstiges 5 J.).
- Zugriffsressourcen und Lizenzen werden beim CSV-Import automatisch im
  Ziel-Inventarbereich angelegt; Lizenz-Seats werden initial auf die Anzahl der
  importierten Zuweisungen gesetzt und sollten danach gepflegt werden.
- Der Garantie-Vorlauf ist je Inventarbereich konfigurierbar (Standard 90 Tage).
- `MAIL_MAILER=log` bis ein Postfach `inventory@barox.io` bereitsteht.
- ID-Token-Validierung per Claims-Prüfung über TLS-Kanal (OIDC Core 3.1.3.7);
  Signaturprüfung entfällt bewusst, da das Token direkt vom Token-Endpunkt bezogen wird.
