Add Entra group restriction, button-only mode and detailed README

Groups: a Graph-backed picker on the Security tab (search by name or
paste object IDs) stores allowed group IDs. During sign-in membership is
read from the ID token's groups claim when present, otherwise verified
through Microsoft Graph checkMemberGroups (transitive). Verification
failures refuse the sign-in.

Button-only mode: hides the password form and the lost-password link
and rejects password sign-ins on wp-login.php via the authenticate
filter. A generated, rate-limited fallback key re-enables the form for
30 minutes per browser; M365_LOGIN_DISABLE_BUTTON_ONLY switches the
mode off from wp-config.php.

Also: new German-language README with sequence diagram, settings
reference, troubleshooting and hook examples; readme.txt external
services section now covers Microsoft Graph; translations updated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJxAHYdMfKPoN4koRc4Ci2
This commit is contained in:
friloo 2026-09-22 14:30:53 +00:00
parent 1517e7e3bc
commit 1202283eda
No known key found for this signature in database
20 changed files with 2241 additions and 517 deletions

414
README.md
View file

@ -1,94 +1,394 @@
# M365 Login für WordPress
<div align="center">
Ein schlankes, sicherheitsorientiertes WordPress-Plugin, das einen **„Login mit Microsoft“-Button** auf die
Anmeldeseite (`wp-login.php`) setzt. Bestehende WordPress-Benutzer melden sich mit ihrem Microsoft 365 /
Entra-ID-Konto an. Der gemeinsame Schlüssel ist die **E-Mail-Adresse** es werden keine Benutzer angelegt.
<img src=".wordpress-org/icon.svg" width="96" height="96" alt="">
> Plugin-Slug / Text Domain: `m365-login` · Lizenz: GPL-2.0-or-later · PHP ≥ 7.4 · WordPress ≥ 6.0
# M365 Login
## Funktionen
**Anmeldung an WordPress mit dem Microsoft 365 / Entra ID-Konto sicher, schlank, gestaltbar.**
- **Button auf der Login-Seite** Text, Icon (Microsoft-Logo oder eigenes Bild aus der Mediathek), Hintergrund-,
Hover-, Text- und Rahmenfarbe, Eckenradius und Position (über/unter dem Formular) sind im Backend einstellbar,
mit Live-Vorschau und Farb-Presets.
- **Aufgeräumte Einstellungsseite** unter *Einstellungen → M365 Login* mit Redirect-URI zum Kopieren,
Tenant-Verbindungstest und 5-Schritte-Anleitung.
- **Kein Provisioning**: Anmeldung nur, wenn ein WordPress-Benutzer mit derselben E-Mail-Adresse existiert.
- **Shortcode** `[m365_login_button redirect="/mein-konto/"]` für eigene Login-Seiten.
- Vollständig übersetzbar, deutsche Übersetzung enthalten.
[![WordPress](https://img.shields.io/badge/WordPress-6.0%2B-21759b?logo=wordpress&logoColor=white)](https://wordpress.org/)
[![PHP](https://img.shields.io/badge/PHP-7.4%2B-777bb4?logo=php&logoColor=white)](https://www.php.net/)
[![License](https://img.shields.io/badge/Lizenz-GPL--2.0--or--later-blue.svg)](LICENSE)
[![CI](https://github.com/friloo/wp-m365-login/actions/workflows/ci.yml/badge.svg)](https://github.com/friloo/wp-m365-login/actions/workflows/ci.yml)
[![Plugin Check](https://img.shields.io/badge/WordPress.org-Plugin%20Check%20ready-46b450)](docs/wordpress-org-einreichung.md)
## Sicherheit
<img src=".github/assets/button-preview.svg" width="720" alt="Login-Seite mit Microsoft-Button, Farb-Presets und Sicherheitsmerkmalen">
| Maßnahme | Umsetzung |
</div>
---
## Inhalt
- [Auf einen Blick](#auf-einen-blick)
- [So funktioniert es](#so-funktioniert-es)
- [Installation](#installation)
- [Einrichtung in Microsoft Entra ID](#einrichtung-in-microsoft-entra-id)
- [Einstellungen im Backend](#einstellungen-im-backend)
- [Verbindung](#verbindung)
- [Button](#button)
- [Sicherheit](#sicherheit)
- [Gruppen-Beschränkung](#gruppen-beschränkung)
- [Nur-Button-Modus & Fallback](#nur-button-modus--fallback)
- [Sicherheitskonzept](#sicherheitskonzept)
- [Shortcode & Hooks](#shortcode--hooks)
- [Fehlerbehebung](#fehlerbehebung)
- [Entwicklung](#entwicklung)
- [Einreichung bei WordPress.org](#einreichung-bei-wordpressorg)
- [FAQ](#faq)
- [Lizenz](#lizenz)
---
## Auf einen Blick
| | |
| --- | --- |
| Authorization Code Flow **mit PKCE (S256)** | Tokens laufen ausschließlich Server-zu-Server, nie durch den Browser. |
| **State & Nonce** | Einmalig, 10 Minuten gültig, per HttpOnly/SameSite-Cookie an den Browser gebunden (CSRF-/Replay-Schutz, verhindert Login-CSRF). |
| **ID-Token-Prüfung** | Signatur gegen Microsofts JWKS (RS256, Schlüssel-Rollover wird abgefangen), Issuer, Audience, Tenant, `exp`/`nbf`/`iat`, Nonce. `alg=none`/HMAC werden abgelehnt. |
| **Tenant-Pinning** | Bei konfigurierter Tenant-GUID werden Tokens anderer Tenants abgewiesen. |
| **Konto-Bindung** | Beim ersten Login wird die unveränderliche Microsoft-Objekt-ID am Benutzer gespeichert; spätere Logins mit gleicher E-Mail, aber anderer Identität werden abgelehnt. |
| **Domain-Allowlist** | Optional nur bestimmte E-Mail-Domains zulassen. |
| **Client Secret verschlüsselt** | AES-256-GCM, Schlüssel aus den WordPress-Salts abgeleitet; wird nie wieder angezeigt. |
| **WordPress-Standards** | Capability-Checks, Nonces, Sanitizing aller Eingaben, Escaping aller Ausgaben, `wp_safe_redirect`, keine externen Assets. |
| 🔑 **Login per Microsoft** | Ein Klick auf der Anmeldeseite, Anmeldung bei Microsoft, zurück in WordPress fertig. |
| 📧 **Zuordnung über die E-Mail-Adresse** | Es werden **keine Benutzer angelegt**. Nur wer schon ein WordPress-Konto mit derselben E-Mail hat, kommt rein. |
| 🎨 **Gestaltbarer Button** | Text, Icon (Microsoft-Logo oder eigenes Bild), Farben, Hover-Farbe, Rahmen, Eckenradius, Position mit Live-Vorschau und Presets. |
| 👥 **Entra-Gruppen** | Optional nur Mitglieder ausgewählter Gruppen zulassen. Gruppen werden direkt im Backend gesucht und ausgewählt. |
| 🚪 **Nur-Button-Modus** | Passwortfelder ausblenden und Passwort-Logins sperren mit geheimem Fallback-Link als Notausgang. |
| 🛡️ **Sicher by default** | OpenID Connect + PKCE, Signaturprüfung, Tenant-Pinning, Konto-Bindung, verschlüsseltes Secret. |
| 🌍 **Übersetzbar** | Englische Basis, deutsche Übersetzung (du & Sie) enthalten. |
| 📦 **WordPress.org-ready** | readme.txt, Lizenz, Uninstall, Plugin Check in CI, Build-Script. |
## Installation & Einrichtung
---
1. Ordner in `wp-content/plugins/` legen (oder ZIP aus `bin/build-zip.sh` hochladen) und aktivieren.
2. *Einstellungen → M365 Login* öffnen und die **Redirect-URI** aus der Seitenleiste kopieren
(`https://deine-seite.tld/m365-login/callback`).
3. Im [Microsoft Entra Admin Center](https://entra.microsoft.com/) → **App-Registrierungen → Neue Registrierung**:
- Name frei wählbar, z. B. „WordPress Login“.
- Kontotypen: *Nur Konten in diesem Organisationsverzeichnis* (Single Tenant).
- Plattform **Web**, Redirect-URI einfügen.
4. Auf der Übersichtsseite **Anwendungs-ID (Client)** und **Verzeichnis-ID (Mandant)** kopieren und im Plugin eintragen.
5. **Zertifikate & Geheimnisse → Neuer geheimer Clientschlüssel** den *Wert* (nicht die ID) ins Plugin eintragen.
Ablaufdatum notieren; abgelaufene Secrets müssen erneuert werden.
6. **Tokenkonfiguration → Optionalen Anspruch hinzufügen → ID → `email`** (empfohlen). Die delegierten
Berechtigungen `openid`, `profile`, `email` sind standardmäßig vorhanden.
7. Speichern. Der Button erscheint auf `wp-login.php`; Gestaltung im Tab **Button**.
## So funktioniert es
Stelle sicher, dass die E-Mail-Adressen der WordPress-Benutzer mit denen in Microsoft 365 übereinstimmen.
```mermaid
sequenceDiagram
autonumber
participant B as Browser
participant WP as WordPress<br/>(M365 Login)
participant MS as Microsoft Entra ID
participant G as Microsoft Graph<br/>(optional)
## Entwickler-Hooks
B->>WP: Klick auf „Login mit Microsoft“
WP->>WP: state, nonce, PKCE-Verifier erzeugen<br/>State-Cookie setzen (HttpOnly)
WP-->>B: Redirect zu Microsoft (code_challenge, state, nonce)
B->>MS: Anmeldung beim Microsoft-Konto
MS-->>B: Redirect zurück mit code + state
B->>WP: /m365-login/callback?code=…&state=…
WP->>WP: State einmalig einlösen, Cookie prüfen
WP->>MS: Code + code_verifier + Client Secret (Server-zu-Server)
MS-->>WP: ID-Token
WP->>MS: Signaturschlüssel (JWKS, gecacht)
WP->>WP: Signatur, Issuer, Audience, Tenant, exp, Nonce prüfen
opt Gruppen-Beschränkung aktiv
WP->>G: checkMemberGroups(oid, erlaubte Gruppen)
G-->>WP: Treffer / kein Treffer
end
WP->>WP: Benutzer per E-Mail suchen, Objekt-ID abgleichen
WP-->>B: WordPress-Session, Redirect ins Dashboard
```
Tokens laufen ausschließlich zwischen deinem Server und Microsoft. Der Browser sieht nur einen Autorisierungscode, der ohne den serverseitigen PKCE-Verifier und das Client Secret wertlos ist.
---
## Installation
**Variante A manuell (empfohlen, solange das Plugin nicht im Verzeichnis ist)**
```bash
git clone https://github.com/friloo/wp-m365-login.git
cd wp-m365-login
bash bin/build-zip.sh # erzeugt build/m365-login.zip
```
Dann in WordPress unter *Plugins → Installieren → Plugin hochladen* das ZIP hochladen und aktivieren.
Alternativ den Repo-Inhalt als Ordner `m365-login` nach `wp-content/plugins/` kopieren.
**Variante B WordPress.org** (nach der Freigabe): *Plugins → Installieren → „M365 Login“*.
> **Voraussetzungen:** WordPress ≥ 6.0, PHP ≥ 7.4 mit OpenSSL-Erweiterung, HTTPS auf der Website (Microsoft akzeptiert `http://` nur für `localhost`).
---
## Einrichtung in Microsoft Entra ID
<details open>
<summary><strong>Schritt für Schritt (ca. 5 Minuten)</strong></summary>
1. **Redirect-URI kopieren.** In WordPress *Einstellungen → M365 Login* öffnen; die URI steht in der Seitenleiste
(`https://deine-seite.tld/m365-login/callback`, bei einfachen Permalinks `https://deine-seite.tld/?m365-login=callback`).
2. **App registrieren.** [Microsoft Entra Admin Center](https://entra.microsoft.com/) → *App-Registrierungen → Neue Registrierung*
- Name: z. B. „WordPress Login“
- Unterstützte Kontotypen: **Nur Konten in diesem Organisationsverzeichnis** (Single Tenant)
- Umleitungs-URI: Plattform **Web**, URI aus Schritt 1
3. **IDs übernehmen.** Auf der Übersichtsseite **Anwendungs-ID (Client)** und **Verzeichnis-ID (Mandant)** kopieren → in WordPress eintragen.
4. **Client Secret erstellen.** *Zertifikate & Geheimnisse → Neuer geheimer Clientschlüssel* → den **Wert** (nicht die Geheimnis-ID) in WordPress eintragen.
Das Ablaufdatum notieren abgelaufene Secrets müssen erneuert werden.
5. **E-Mail-Claim aktivieren** (empfohlen). *Tokenkonfiguration → Optionalen Anspruch hinzufügen → ID → `email`*.
6. **Speichern** und mit *Tenant testen* prüfen, ob Microsoft erreichbar ist.
</details>
<details>
<summary><strong>Zusätzlich für die Gruppen-Beschränkung</strong></summary>
Damit das Backend Gruppen suchen und beim Login die Mitgliedschaft prüfen kann, braucht die App-Registrierung
**Anwendungsberechtigungen** (nicht delegiert) für Microsoft Graph, jeweils mit **Administratorzustimmung**:
| Berechtigung | Wofür |
| --- | --- |
| `GroupMember.Read.All` | Gruppen im Backend suchen |
| `User.Read.All` | Mitgliedschaft beim Login prüfen (`checkMemberGroups`, inkl. verschachtelter Gruppen) |
`Directory.Read.All` deckt beides ab, ist aber weiter gefasst.
**Ohne Graph-Berechtigungen** geht es auch: Unter *Tokenkonfiguration → Gruppenanspruch hinzufügen* den `groups`-Claim
für ID-Tokens aktivieren (am besten *Der Anwendung zugewiesene Gruppen* oder *Sicherheitsgruppen*). Dann prüft das Plugin die
Mitgliedschaft direkt im Token. Gruppen-IDs lassen sich im Backend auch von Hand einfügen. Bei mehr als 200 Gruppen pro
Benutzer liefert Microsoft keinen `groups`-Claim mehr („Overage“); dann greift das Plugin automatisch auf Graph zurück.
</details>
Wichtig: Jeder Benutzer, der sich per Microsoft anmelden soll, braucht in WordPress **dieselbe E-Mail-Adresse** wie in Microsoft 365.
---
## Einstellungen im Backend
*Einstellungen → M365 Login* drei Tabs, ein Formular, ein Speichern-Button.
### Verbindung
| Feld | Beschreibung |
| --- | --- |
| Verzeichnis-ID (Tenant) | GUID des Tenants (empfohlen, aktiviert Tenant-Pinning) oder `organizations` / `common` / `consumers`. |
| Anwendungs-ID (Client) | GUID der App-Registrierung. |
| Client Secret | Wird verschlüsselt gespeichert und nie wieder angezeigt. Leer lassen = behalten. |
| Kontoauswahl | `select_account` (Standard), `none` (bestehende Microsoft-Sitzung nutzen) oder `login` (immer Anmeldedaten verlangen). |
| Tenant testen | Lädt die OpenID-Konfiguration des Tenants prüft ID und ausgehende Verbindung. |
### Button
| Option | Beschreibung |
| --- | --- |
| Button-Text | Standard „Sign in with Microsoft“ / „Login mit Microsoft“ (max. 80 Zeichen). |
| Trennlinien-Text | Standard „or“ / „oder“; leer = keine Trennlinie. |
| Icon | Microsoft-Logo (eingebettet) oder eigenes Bild aus der Mediathek (PNG, SVG, JPG, WebP). Ein-/ausblendbar. |
| Farben | Hintergrund, Hintergrund (Hover), Text, Rahmen mit Farbwähler. |
| Eckenradius | 050 px. |
| Position | Unter dem Login-Formular (Standard) oder darüber. |
| Presets | Microsoft dunkel, Microsoft hell, Azure-Blau, WordPress-Blau. |
Alles wird live in der Vorschau angezeigt, bevor du speicherst.
### Sicherheit
| Option | Standard | Beschreibung |
| --- | --- | --- |
| Konto an Microsoft-Objekt-ID binden | an | Beim ersten Login wird die `oid` gespeichert; danach muss sie übereinstimmen. Schützt vor Übernahme, wenn eine E-Mail-Adresse in Microsoft neu vergeben wird. |
| UPN-Fallback | an | Fehlt der `email`-Claim, wird der User Principal Name verwendet, sofern er eine gültige E-Mail-Adresse ist. |
| Angemeldet bleiben | aus | 14-Tage-Session statt Browser-Session. |
| Erlaubte E-Mail-Domains | leer | Kommagetrennte Liste, z. B. `contoso.com, contoso.de`. |
### Gruppen-Beschränkung
Im Tab *Sicherheit***Erlaubte Entra-Gruppen**:
1. Gruppenname eintippen (oder Objekt-ID einfügen) → *Suchen*.
2. Treffer mit *Hinzufügen* übernehmen sie erscheinen als Chips mit Name und ID.
3. Speichern. Ab jetzt darf sich nur anmelden, wer in **mindestens einer** dieser Gruppen ist (verschachtelte Mitgliedschaften zählen).
Prüfreihenfolge beim Login:
1. Enthält das ID-Token einen `groups`-Claim → Abgleich direkt im Token.
2. Sonst (oder bei Overage) → Microsoft Graph `checkMemberGroups`.
3. Schlägt beides fehl → **Anmeldung abgelehnt** (fail closed), Meldung „Gruppenmitgliedschaft konnte nicht geprüft werden“.
Leere Liste = keine Beschränkung.
### Nur-Button-Modus & Fallback
Im Tab *Sicherheit***Button-only mode**:
- Blendet Benutzername/Passwort-Felder und den „Passwort vergessen?“-Link aus.
- **Sperrt Passwort-Logins** über `wp-login.php` serverseitig nicht nur per CSS.
- Application Passwords, REST API und XML-RPC sind nicht betroffen.
- Wird erst aktiv, wenn die Verbindung vollständig konfiguriert ist.
**Fallback (Notausgang):** Beim Speichern erzeugt das Plugin einen geheimen Schlüssel und zeigt den Fallback-Link an:
```
https://deine-seite.tld/wp-login.php?m365_fallback=AbC…xYz
```
Wer den Link öffnet, sieht für **30 Minuten in diesem Browser** wieder das normale Formular und kann sich mit Passwort anmelden.
Der Schlüssel landet nicht im Cookie (nur ein HMAC davon), Fehlversuche werden pro IP gedrosselt (10 Versuche / 15 Minuten),
und über die Checkbox *Neuen Schlüssel beim Speichern erzeugen* lässt er sich jederzeit rotieren.
**Notschalter ohne Backend-Zugang:** In `wp-config.php`
```php
// Button z. B. nur für eine bestimmte Domain anzeigen
add_filter( 'm365_login_show_button', fn( $show ) => $show && 'intranet.example.com' === $_SERVER['HTTP_HOST'] );
define( 'M365_LOGIN_DISABLE_BUTTON_ONLY', true );
```
// domain_hint an Microsoft senden
schaltet den Modus komplett ab. Alternativ das Plugin-Verzeichnis per FTP umbenennen.
> ⚠️ Vor dem Aktivieren sicherstellen, dass dein eigenes Admin-Konto per Microsoft funktioniert, und den Fallback-Link sicher ablegen.
---
## Sicherheitskonzept
| Bedrohung | Gegenmaßnahme |
| --- | --- |
| Abfangen von Tokens im Browser | Authorization Code Flow **mit PKCE (S256)**; ID-Token wird serverseitig geholt, `response_mode=query` ohne Token. |
| CSRF / Login-CSRF | `state` ist zufällig (256 Bit), **einmalig** verwendbar, 10 Min. gültig und per HttpOnly-/SameSite-Cookie an den startenden Browser gebunden. |
| Token-Replay | `nonce` wird im ID-Token geprüft und mit dem State-Datensatz verworfen. |
| Gefälschte Tokens | Signaturprüfung gegen Microsofts JWKS (`RS256` only; `alg=none`/HMAC werden abgelehnt), Schlüssel-Rollover wird automatisch nachgeladen. `iss`, `aud`, `tid`, `exp`, `nbf`, `iat` werden geprüft. |
| Fremde Tenants | Bei konfigurierter Tenant-GUID **Tenant-Pinning**; sonst Issuer-Konsistenz mit `tid`. |
| Kontoübernahme per E-Mail-Recycling | **Bindung an die Objekt-ID** (`oid`) beim ersten Login. |
| Unbefugte Konten | Kein Provisioning, optionale Domain-Allowlist, optionale Gruppen-Beschränkung (fail closed). |
| Secret-Diebstahl aus der Datenbank | AES-256-GCM, Schlüssel per HKDF aus `AUTH_KEY`/`SECURE_AUTH_KEY`; ohne `wp-config.php` ist der Datensatz wertlos. |
| Offene Redirects | `redirect_to` läuft durch `wp_validate_redirect`, alle Redirects über `wp_safe_redirect`. |
| Fehler-Reflektion | Fehlermeldungen sind Codes → feste, übersetzte Texte; Details nur ins Log (`WP_DEBUG_LOG`). |
| Rate Limiting Fallback-Key | 10 Fehlversuche pro IP / 15 Min. |
Die Klassen für JWT-Prüfung und Verschlüsselung haben isolierte Tests (manipulierte Signaturen, abgelaufene Tokens, falsche Audience/Tenant/Issuer, `alg=none`, fremde Schlüssel).
---
## Shortcode & Hooks
**Shortcode** für eigene Login-Seiten:
```
[m365_login_button redirect="/mein-konto/"]
```
**Filter & Actions**
```php
// Button z. B. nur im Intranet zeigen
add_filter( 'm365_login_show_button', function ( $show ) {
return $show && 'intranet.example.com' === $_SERVER['HTTP_HOST'];
} );
// domain_hint mitschicken, damit Microsoft direkt die Firmenanmeldung zeigt
add_filter( 'm365_login_authorize_params', function ( $params ) {
$params['domain_hint'] = 'contoso.com';
return $params;
} );
// Login zusätzlich anhand der Claims verbieten (z. B. Gruppenmitgliedschaft)
// E-Mail vor dem Lookup umschreiben (z. B. Alias-Domain)
add_filter( 'm365_login_match_email', function ( $email, $claims ) {
return str_replace( '@alt.contoso.com', '@contoso.com', $email );
}, 10, 2 );
// Eigene Zusatzprüfung nach allen Plugin-Checks
add_filter( 'm365_login_allow_user', function ( $allowed, WP_User $user, array $claims ) {
return $allowed && ! empty( $claims['groups'] );
return $allowed && ! in_array( 'subscriber', $user->roles, true );
}, 10, 3 );
// Nach erfolgreichem Login, z. B. Anzeigenamen synchronisieren
add_action( 'm365_login_success', function ( WP_User $user, array $claims ) {
// z. B. Anzeigenamen synchronisieren
if ( ! empty( $claims['name'] ) ) {
wp_update_user( array( 'ID' => $user->ID, 'display_name' => $claims['name'] ) );
}
}, 10, 2 );
// Redirect-URI anpassen (z. B. hinter einem Reverse Proxy)
add_filter( 'm365_login_redirect_uri', fn( $uri ) => 'https://www.example.com/m365-login/callback' );
```
Weitere: `m365_login_match_email` (E-Mail vor dem Lookup anpassen).
---
## Fehlerbehebung
| Meldung auf der Login-Seite | Ursache & Lösung |
| --- | --- |
| *Microsoft login is not configured yet.* | Tenant-ID, Client-ID oder Secret fehlt. |
| *The login request expired or was invalid.* | State abgelaufen (> 10 Min.), Cookie blockiert oder Seite doppelt geladen. Erneut versuchen; Cookies für die Domain erlauben. |
| *Could not complete the sign-in with Microsoft.* | Token-Tausch fehlgeschlagen meist falsches/abgelaufenes Client Secret oder Redirect-URI stimmt nicht exakt mit Entra überein. Details im Log. |
| *The Microsoft sign-in could not be verified.* | ID-Token abgelehnt (Tenant, Audience, Signatur). Tenant-ID prüfen; Serverzeit prüfen (NTP). |
| *Your Microsoft account did not provide an e-mail address.* | `email`-Claim fehlt und UPN-Fallback ist aus oder UPN ist keine E-Mail. Claim in der Tokenkonfiguration hinzufügen. |
| *No WordPress account exists for your Microsoft e-mail address.* | E-Mail in WordPress stimmt nicht mit Microsoft überein. |
| *This WordPress account is linked to a different Microsoft account.* | Objekt-ID weicht ab. Wenn gewollt (neues Microsoft-Konto): User-Meta `_m365_login_oid` beim Benutzer löschen. |
| *… not a member of a group that is allowed …* | Benutzer ist in keiner der ausgewählten Gruppen. |
| *Your group membership could not be verified.* | Graph nicht erreichbar oder Berechtigung fehlt (`User.Read.All`) oder `groups`-Claim aktivieren. |
Logging: Mit `WP_DEBUG` und `WP_DEBUG_LOG` schreibt das Plugin Fehlerdetails mit Präfix `[M365 Login]` nach `wp-content/debug.log`. Es werden nie Tokens oder Secrets geloggt.
---
## Entwicklung
```bash
composer install # PHPCS + WordPress Coding Standards
composer lint # php -l über alle Dateien
composer phpcs # Coding-Standards-Prüfung
bash bin/build-zip.sh # build/m365-login.zip für Upload/Einreichung
python3 bin/compile-mo.py # languages/*.po → *.mo
```
m365-login.php Plugin-Header & Bootstrap
includes/
class-m365-login.php Verdrahtung der Komponenten
class-m365-login-settings.php Defaults, Sanitizing, Redirect-URI, Fallback-Key
class-m365-login-crypto.php AES-256-GCM für das Client Secret
class-m365-login-jwt.php RS256-Verifikation, JWKS → PEM
class-m365-login-auth.php OAuth-Flow, Callback, Benutzerzuordnung, Nur-Button-Modus
class-m365-login-graph.php Client-Credentials-Token, Gruppensuche, checkMemberGroups
class-m365-login-button.php Ausgabe auf wp-login.php, Shortcode
class-m365-login-admin.php Einstellungsseite, AJAX
assets/ CSS/JS für Login-Seite und Backend (unminifiziert)
languages/ .pot, de_DE, de_DE_formal
bin/ build-zip.sh, make-pot.py, compile-mo.py
docs/ Einreichungs-Checkliste
```
Die GitHub-Actions-Pipeline (`.github/workflows/ci.yml`) führt Syntax-Check (PHP 7.48.4), PHPCS und den
offiziellen **WordPress Plugin Check** aus.
```bash
composer install # PHPCS + WordPress Coding Standards + PHPCompatibility
composer lint # php -l für alle Dateien
composer phpcs # Coding-Standards-Prüfung (phpcs.xml.dist)
python3 bin/make-pot.py # Strings extrahieren (oder: wp i18n make-pot . languages/m365-login.pot)
python3 bin/compile-mo.py # .po → .mo
bash bin/build-zip.sh # build/m365-login.zip
```
Die CI (`.github/workflows/ci.yml`) prüft Syntax unter PHP 7.48.4, führt PHPCS aus und lässt den offiziellen
**WordPress Plugin Check** über das Build-Verzeichnis laufen.
---
## Einreichung bei WordPress.org
Siehe [docs/wordpress-org-einreichung.md](docs/wordpress-org-einreichung.md) für die vollständige Checkliste.
Das Plugin bringt alles mit, was das Review-Team verlangt: `readme.txt` mit *External services*-Abschnitt, GPL-Lizenz,
`uninstall.php`, eindeutige Präfixe, keine externen Assets, Übersetzungen, Verzeichnis-Icon. Die komplette Checkliste
(inkl. Slug-/Marken-Hinweisen und SVN-Schritten nach der Freigabe) steht in
**[docs/wordpress-org-einreichung.md](docs/wordpress-org-einreichung.md)**.
---
## FAQ
<details>
<summary><strong>Kann ich Benutzer automatisch anlegen lassen?</strong></summary>
Nein, bewusst nicht. Der Admin entscheidet, wer ein Konto hat. Wer Auto-Provisioning braucht, kann es über den Hook <code>m365_login_allow_user</code> nicht nachrüsten das wäre ein anderes Sicherheitsmodell.
</details>
<details>
<summary><strong>Funktioniert es mit privaten Microsoft-Konten (outlook.com)?</strong></summary>
Ja, Tenant auf <code>consumers</code> oder <code>common</code> stellen. Microsoft erlaubt dann keine Query-Strings in Redirect-URIs, deshalb müssen sprechende Permalinks aktiv sein (Callback ohne <code>?</code>).
</details>
<details>
<summary><strong>Multisite?</strong></summary>
Ja. Einstellungen gelten pro Site; der Benutzer muss Mitglied der Site (oder Super-Admin) sein.
</details>
<details>
<summary><strong>Was passiert beim Deinstallieren?</strong></summary>
Einstellungen, Caches (Transients) und die pro Benutzer gespeicherte Objekt-ID werden entfernt auch in Multisite.
</details>
<details>
<summary><strong>Ich habe mich ausgesperrt.</strong></summary>
Fallback-Link öffnen. Kein Link zur Hand? <code>define( 'M365_LOGIN_DISABLE_BUTTON_ONLY', true );</code> in die <code>wp-config.php</code> oder den Plugin-Ordner per FTP umbenennen.
</details>
---
## Lizenz
GPL-2.0-or-later siehe [LICENSE](LICENSE).
GPL-2.0-or-later siehe [LICENSE](LICENSE). „Microsoft“, „Microsoft 365“ und das Microsoft-Logo sind Marken der Microsoft Corporation;
das Plugin ist ein unabhängiges Community-Projekt und steht in keiner Verbindung zu Microsoft.