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:
parent
1517e7e3bc
commit
1202283eda
20 changed files with 2241 additions and 517 deletions
64
.github/assets/button-preview.svg
vendored
Normal file
64
.github/assets/button-preview.svg
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="720" height="300" viewBox="0 0 720 300" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#f6f7f7"/>
|
||||
<stop offset="1" stop-color="#e9edf2"/>
|
||||
</linearGradient>
|
||||
<filter id="shadow" x="-10%" y="-10%" width="120%" height="130%">
|
||||
<feDropShadow dx="0" dy="6" stdDeviation="10" flood-color="#0b1d33" flood-opacity="0.12"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<rect width="720" height="300" rx="20" fill="url(#bg)"/>
|
||||
|
||||
<!-- Login card -->
|
||||
<g transform="translate(200 34)" filter="url(#shadow)">
|
||||
<rect width="320" height="232" rx="6" fill="#fff" stroke="#c3c4c7"/>
|
||||
<!-- fake fields -->
|
||||
<text x="24" y="38" font-size="13" fill="#3c434a">Benutzername oder E-Mail-Adresse</text>
|
||||
<rect x="24" y="48" width="272" height="34" rx="4" fill="#fff" stroke="#8c8f94"/>
|
||||
<text x="24" y="108" font-size="13" fill="#3c434a">Passwort</text>
|
||||
<rect x="24" y="118" width="272" height="34" rx="4" fill="#fff" stroke="#8c8f94"/>
|
||||
<!-- divider -->
|
||||
<line x1="24" y1="180" x2="296" y2="180" stroke="#dcdcde"/>
|
||||
<rect x="140" y="171" width="40" height="18" fill="#fff"/>
|
||||
<text x="160" y="184" font-size="12" fill="#646970" text-anchor="middle">oder</text>
|
||||
<!-- Microsoft button -->
|
||||
<rect x="24" y="196" width="272" height="44" rx="4" fill="#2f2f2f"/>
|
||||
<g transform="translate(70 206)">
|
||||
<rect width="24" height="24" rx="2" fill="#fff"/>
|
||||
<rect x="4" y="4" width="7" height="7" fill="#f25022"/>
|
||||
<rect x="13" y="4" width="7" height="7" fill="#7fba00"/>
|
||||
<rect x="4" y="13" width="7" height="7" fill="#00a4ef"/>
|
||||
<rect x="13" y="13" width="7" height="7" fill="#ffb900"/>
|
||||
</g>
|
||||
<text x="180" y="223" font-size="15" font-weight="600" fill="#fff" text-anchor="middle">Login mit Microsoft</text>
|
||||
</g>
|
||||
|
||||
<!-- Colour swatches -->
|
||||
<g transform="translate(40 60)">
|
||||
<text x="0" y="0" font-size="12" fill="#646970" font-weight="600">PRESETS</text>
|
||||
<g transform="translate(0 16)">
|
||||
<rect width="120" height="34" rx="17" fill="#2f2f2f"/><text x="60" y="22" font-size="12" fill="#fff" text-anchor="middle" font-weight="600">Microsoft dark</text>
|
||||
</g>
|
||||
<g transform="translate(0 60)">
|
||||
<rect width="120" height="34" rx="17" fill="#fff" stroke="#8c8c8c"/><text x="60" y="22" font-size="12" fill="#5e5e5e" text-anchor="middle" font-weight="600">Microsoft light</text>
|
||||
</g>
|
||||
<g transform="translate(0 104)">
|
||||
<rect width="120" height="34" rx="17" fill="#0078d4"/><text x="60" y="22" font-size="12" fill="#fff" text-anchor="middle" font-weight="600">Azure blue</text>
|
||||
</g>
|
||||
<g transform="translate(0 148)">
|
||||
<rect width="120" height="34" rx="17" fill="#2271b1"/><text x="60" y="22" font-size="12" fill="#fff" text-anchor="middle" font-weight="600">WordPress blue</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Right: security badges -->
|
||||
<g transform="translate(560 60)" font-size="12" fill="#1d2327">
|
||||
<text x="0" y="0" font-size="12" fill="#646970" font-weight="600">SICHERHEIT</text>
|
||||
<g transform="translate(0 16)"><circle cx="8" cy="8" r="8" fill="#1e6b31"/><path d="M4.5 8.5l2.5 2.5 4.5-5" fill="none" stroke="#fff" stroke-width="2"/><text x="24" y="12">PKCE (S256)</text></g>
|
||||
<g transform="translate(0 44)"><circle cx="8" cy="8" r="8" fill="#1e6b31"/><path d="M4.5 8.5l2.5 2.5 4.5-5" fill="none" stroke="#fff" stroke-width="2"/><text x="24" y="12">State + Nonce</text></g>
|
||||
<g transform="translate(0 72)"><circle cx="8" cy="8" r="8" fill="#1e6b31"/><path d="M4.5 8.5l2.5 2.5 4.5-5" fill="none" stroke="#fff" stroke-width="2"/><text x="24" y="12">JWKS-Signatur</text></g>
|
||||
<g transform="translate(0 100)"><circle cx="8" cy="8" r="8" fill="#1e6b31"/><path d="M4.5 8.5l2.5 2.5 4.5-5" fill="none" stroke="#fff" stroke-width="2"/><text x="24" y="12">Tenant-Pinning</text></g>
|
||||
<g transform="translate(0 128)"><circle cx="8" cy="8" r="8" fill="#1e6b31"/><path d="M4.5 8.5l2.5 2.5 4.5-5" fill="none" stroke="#fff" stroke-width="2"/><text x="24" y="12">Secret verschlüsselt</text></g>
|
||||
<g transform="translate(0 156)"><circle cx="8" cy="8" r="8" fill="#1e6b31"/><path d="M4.5 8.5l2.5 2.5 4.5-5" fill="none" stroke="#fff" stroke-width="2"/><text x="24" y="12">Gruppen-Check</text></g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
|
|
@ -13,5 +13,7 @@ All notable changes to this project are documented in this file. The format foll
|
|||
- ID token verification against Microsoft's JWKS (RS256, issuer, audience, tenant, expiry, nonce).
|
||||
- Encrypted client secret storage (AES-256-GCM).
|
||||
- Account binding to the Microsoft object ID, e-mail domain allow-list.
|
||||
- Entra group restriction with a Graph-powered group picker; membership verified via the `groups` claim or Microsoft Graph `checkMemberGroups`.
|
||||
- Button-only mode that hides the password form and blocks password sign-in on `wp-login.php`, with a secret, rate-limited fallback link and a `wp-config.php` emergency constant.
|
||||
- `[m365_login_button]` shortcode and developer hooks.
|
||||
- German translation.
|
||||
|
|
|
|||
414
README.md
414
README.md
|
|
@ -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.
|
||||
[](https://wordpress.org/)
|
||||
[](https://www.php.net/)
|
||||
[](LICENSE)
|
||||
[](https://github.com/friloo/wp-m365-login/actions/workflows/ci.yml)
|
||||
[](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 | 0–50 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.4–8.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.4–8.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.
|
||||
|
|
|
|||
|
|
@ -576,3 +576,145 @@
|
|||
.m365-card code {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Group picker */
|
||||
.m365-group-results {
|
||||
margin-top: 10px;
|
||||
border: 1px solid var(--m365-border);
|
||||
border-radius: 8px;
|
||||
max-height: 320px;
|
||||
overflow-y: auto;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.m365-group-results.is-error {
|
||||
border-color: #f0b8bd;
|
||||
background: #fcf0f1;
|
||||
}
|
||||
|
||||
.m365-group-results__status {
|
||||
margin: 0;
|
||||
padding: 12px 14px;
|
||||
color: var(--m365-muted);
|
||||
}
|
||||
|
||||
.m365-group-result {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 10px 14px;
|
||||
border-bottom: 1px solid #f0f0f1;
|
||||
}
|
||||
|
||||
.m365-group-result:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.m365-group-result__meta {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: 4px 10px;
|
||||
}
|
||||
|
||||
.m365-group-result__meta code {
|
||||
font-size: 11px;
|
||||
color: var(--m365-muted);
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.m365-group-result__meta em {
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-style: normal;
|
||||
color: var(--m365-muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.m365-group-result__type {
|
||||
font-size: 11px;
|
||||
padding: 1px 8px;
|
||||
border-radius: 999px;
|
||||
background: #f0f6fc;
|
||||
color: #0a4b78;
|
||||
}
|
||||
|
||||
.m365-group-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.m365-group-list.is-empty::before {
|
||||
content: attr(data-empty);
|
||||
color: var(--m365-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.m365-group-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
padding: 6px 6px 6px 12px;
|
||||
background: #f0f6fc;
|
||||
border: 1px solid #c5d9ed;
|
||||
border-radius: 999px;
|
||||
font-size: 13px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.m365-group-chip__name {
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.m365-group-chip__id {
|
||||
font-size: 11px;
|
||||
color: var(--m365-muted);
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.m365-group-chip__remove {
|
||||
appearance: none;
|
||||
border: 0;
|
||||
background: #fff;
|
||||
color: #b32d2e;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
line-height: 1;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.m365-group-chip__remove:hover {
|
||||
background: #b32d2e;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Fallback link */
|
||||
.m365-fallback {
|
||||
margin-top: 8px;
|
||||
padding: 16px;
|
||||
border: 1px dashed #c3c4c7;
|
||||
border-radius: 8px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.m365-fallback .description {
|
||||
margin: 4px 0 8px;
|
||||
}
|
||||
|
||||
.m365-fallback .m365-copy {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,3 +120,17 @@ body.m365-login-attached #loginform {
|
|||
background: transparent;
|
||||
max-width: 360px;
|
||||
}
|
||||
|
||||
/* Button-only mode: the password form is hidden until the fallback key is used. */
|
||||
body.m365-button-only #loginform,
|
||||
body.m365-button-only #nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.m365-button-only .m365-login--below,
|
||||
body.m365-button-only .m365-login--above {
|
||||
margin-top: 20px;
|
||||
padding: 26px 24px;
|
||||
border: 1px solid #c3c4c7;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,6 +159,110 @@
|
|||
}
|
||||
} );
|
||||
|
||||
/* ---------------- Entra group picker ---------------- */
|
||||
var $groupList = $( '#m365-group-list' );
|
||||
var $groupResults = $( '#m365-group-results' );
|
||||
var optionName = ( $( '#m365-tenant' ).attr( 'name' ) || '' ).replace( /\[tenant_id\]$/, '' );
|
||||
|
||||
function escapeHtml( str ) {
|
||||
return $( '<div>' ).text( str || '' ).html();
|
||||
}
|
||||
|
||||
function refreshGroupList() {
|
||||
$groupList.toggleClass( 'is-empty', 0 === $groupList.children( 'li' ).length );
|
||||
}
|
||||
|
||||
function addGroup( id, name ) {
|
||||
id = ( id || '' ).toLowerCase();
|
||||
if ( ! /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test( id ) ) {
|
||||
return;
|
||||
}
|
||||
if ( $groupList.find( 'li[data-id="' + id + '"]' ).length ) {
|
||||
return;
|
||||
}
|
||||
var $li = $( '<li class="m365-group-chip">' ).attr( 'data-id', id );
|
||||
$li.append( $( '<span class="m365-group-chip__name">' ).text( name || id ) );
|
||||
$li.append( $( '<code class="m365-group-chip__id">' ).text( id ) );
|
||||
$li.append( $( '<input type="hidden">' ).attr( 'name', optionName + '[allowed_groups][' + id + ']' ).val( name || id ) );
|
||||
$li.append( $( '<button type="button" class="m365-group-chip__remove" aria-label="' + escapeHtml( i18n.remove ) + '">×</button>' ) );
|
||||
$groupList.append( $li );
|
||||
refreshGroupList();
|
||||
}
|
||||
|
||||
$groupList.on( 'click', '.m365-group-chip__remove', function () {
|
||||
$( this ).closest( 'li' ).remove();
|
||||
refreshGroupList();
|
||||
} );
|
||||
refreshGroupList();
|
||||
|
||||
function searchGroups() {
|
||||
var query = $.trim( $( '#m365-group-search' ).val() );
|
||||
$groupResults.prop( 'hidden', false ).removeClass( 'is-error' ).html( '<p class="m365-group-results__status">' + escapeHtml( i18n.searching ) + '</p>' );
|
||||
|
||||
$.post( cfg.ajaxUrl, {
|
||||
action: cfg.groupAction,
|
||||
nonce: cfg.nonce,
|
||||
query: query
|
||||
} ).done( function ( res ) {
|
||||
if ( ! res || ! res.success ) {
|
||||
var msg = ( res && res.data && res.data.message ) || i18n.testFailed;
|
||||
$groupResults.addClass( 'is-error' ).html( '<p class="m365-group-results__status">' + escapeHtml( msg ) + '</p>' );
|
||||
// Allow adding a pasted GUID even when Graph is unavailable.
|
||||
if ( /^[0-9a-f-]{36}$/i.test( query ) ) {
|
||||
$groupResults.append( buildResult( { id: query, name: query, type: '', description: '' } ) );
|
||||
}
|
||||
return;
|
||||
}
|
||||
var groups = res.data.groups || [];
|
||||
if ( ! groups.length ) {
|
||||
$groupResults.html( '<p class="m365-group-results__status">' + escapeHtml( i18n.noGroups ) + '</p>' );
|
||||
return;
|
||||
}
|
||||
$groupResults.empty();
|
||||
$.each( groups, function ( i, g ) {
|
||||
$groupResults.append( buildResult( g ) );
|
||||
} );
|
||||
} ).fail( function () {
|
||||
$groupResults.addClass( 'is-error' ).html( '<p class="m365-group-results__status">' + escapeHtml( i18n.testFailed ) + '</p>' );
|
||||
} );
|
||||
}
|
||||
|
||||
function buildResult( g ) {
|
||||
var $row = $( '<div class="m365-group-result">' );
|
||||
var $meta = $( '<div class="m365-group-result__meta">' );
|
||||
$meta.append( $( '<strong>' ).text( g.name ) );
|
||||
if ( g.type ) {
|
||||
$meta.append( $( '<span class="m365-group-result__type">' ).text( g.type ) );
|
||||
}
|
||||
$meta.append( $( '<code>' ).text( g.id ) );
|
||||
if ( g.description ) {
|
||||
$meta.append( $( '<em>' ).text( g.description ) );
|
||||
}
|
||||
var $btn = $( '<button type="button" class="button button-small">' ).text( i18n.add ).on( 'click', function () {
|
||||
addGroup( g.id, g.name );
|
||||
$( this ).prop( 'disabled', true );
|
||||
} );
|
||||
if ( $groupList.find( 'li[data-id="' + ( g.id || '' ).toLowerCase() + '"]' ).length ) {
|
||||
$btn.prop( 'disabled', true );
|
||||
}
|
||||
return $row.append( $meta ).append( $btn );
|
||||
}
|
||||
|
||||
$( '#m365-group-search-btn' ).on( 'click', searchGroups );
|
||||
$( '#m365-group-search' ).on( 'keydown', function ( e ) {
|
||||
if ( 'Enter' === e.key ) {
|
||||
e.preventDefault();
|
||||
searchGroups();
|
||||
}
|
||||
} );
|
||||
|
||||
/* ---------------- Fallback key ---------------- */
|
||||
$( '#m365-fallback-regenerate' ).on( 'change', function () {
|
||||
if ( this.checked && ! window.confirm( i18n.confirmKey ) ) {
|
||||
this.checked = false;
|
||||
}
|
||||
} );
|
||||
|
||||
/* ---------------- Test tenant ---------------- */
|
||||
$( '#m365-test' ).on( 'click', function () {
|
||||
var $btn = $( this );
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ erfüllt. Vor der Einreichung unter <https://wordpress.org/plugins/developers/ad
|
|||
| Anforderung | Umsetzung |
|
||||
| --- | --- |
|
||||
| GPL-kompatible Lizenz | `LICENSE` (GPL-2.0), Header in `m365-login.php`, `readme.txt` |
|
||||
| `readme.txt` im WP-Format | inkl. Pflichtabschnitt **External services** (Microsoft-Endpunkte, übertragene Daten, Links zu Nutzungsbedingungen/Datenschutz) |
|
||||
| Keine externen Assets/CDNs | CSS/JS liegen im Plugin; einzige Netzwerkverbindungen gehen zu `login.microsoftonline.com` |
|
||||
| `readme.txt` im WP-Format | inkl. Pflichtabschnitt **External services** (Microsoft-Login- und Graph-Endpunkte, übertragene Daten, Links zu Nutzungsbedingungen/Datenschutz) |
|
||||
| Keine externen Assets/CDNs | CSS/JS liegen im Plugin; einzige Netzwerkverbindungen gehen zu `login.microsoftonline.com` und (optional, Gruppenprüfung) `graph.microsoft.com` |
|
||||
| Sanitizing / Escaping / Nonces | Settings API mit `sanitize_callback`, `esc_*` bei jeder Ausgabe, `check_ajax_referer` + `current_user_can` |
|
||||
| Eindeutiges Präfix | `m365_login_` / `M365_Login_` für alle globalen Bezeichner |
|
||||
| Kein Tracking, keine Telefon-nach-Hause-Funktion | – |
|
||||
|
|
|
|||
|
|
@ -14,8 +14,9 @@ class M365_Login_Admin {
|
|||
|
||||
const PAGE = 'm365-login';
|
||||
const GROUP = 'm365_login';
|
||||
const AJAX_TEST = 'm365_login_test_connection';
|
||||
const NONCE_TEST = 'm365_login_test';
|
||||
const AJAX_TEST = 'm365_login_test_connection';
|
||||
const AJAX_GROUPS = 'm365_login_search_groups';
|
||||
const NONCE_TEST = 'm365_login_test';
|
||||
|
||||
/**
|
||||
* Settings.
|
||||
|
|
@ -31,6 +32,13 @@ class M365_Login_Admin {
|
|||
*/
|
||||
private $auth;
|
||||
|
||||
/**
|
||||
* Graph client.
|
||||
*
|
||||
* @var M365_Login_Graph
|
||||
*/
|
||||
private $graph;
|
||||
|
||||
/**
|
||||
* Screen hook suffix.
|
||||
*
|
||||
|
|
@ -43,15 +51,19 @@ class M365_Login_Admin {
|
|||
*
|
||||
* @param M365_Login_Settings $settings Settings.
|
||||
* @param M365_Login_Auth $auth Auth.
|
||||
* @param M365_Login_Graph $graph Graph client.
|
||||
*/
|
||||
public function __construct( M365_Login_Settings $settings, M365_Login_Auth $auth ) {
|
||||
public function __construct( M365_Login_Settings $settings, M365_Login_Auth $auth, M365_Login_Graph $graph ) {
|
||||
$this->settings = $settings;
|
||||
$this->auth = $auth;
|
||||
$this->graph = $graph;
|
||||
|
||||
add_action( 'admin_menu', array( $this, 'menu' ) );
|
||||
add_action( 'admin_init', array( $this, 'register' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) );
|
||||
add_action( 'wp_ajax_' . self::AJAX_TEST, array( $this, 'ajax_test_connection' ) );
|
||||
add_action( 'wp_ajax_' . self::AJAX_GROUPS, array( $this, 'ajax_search_groups' ) );
|
||||
add_action( 'update_option_' . M365_LOGIN_OPTION, array( $this->graph, 'flush_token' ) );
|
||||
add_action( 'admin_notices', array( $this, 'setup_notice' ) );
|
||||
}
|
||||
|
||||
|
|
@ -127,6 +139,7 @@ class M365_Login_Admin {
|
|||
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
|
||||
'nonce' => wp_create_nonce( self::NONCE_TEST ),
|
||||
'action' => self::AJAX_TEST,
|
||||
'groupAction' => self::AJAX_GROUPS,
|
||||
'defaultLogo' => M365_Login_Button::microsoft_logo(),
|
||||
'i18n' => array(
|
||||
'chooseIcon' => __( 'Choose button icon', 'm365-login' ),
|
||||
|
|
@ -135,6 +148,12 @@ class M365_Login_Admin {
|
|||
'copy' => __( 'Copy', 'm365-login' ),
|
||||
'testing' => __( 'Testing…', 'm365-login' ),
|
||||
'testFailed' => __( 'The tenant could not be reached. Check the tenant ID and the server’s outgoing connections.', 'm365-login' ),
|
||||
'noGroups' => __( 'No groups found.', 'm365-login' ),
|
||||
'searching' => __( 'Searching…', 'm365-login' ),
|
||||
'add' => __( 'Add', 'm365-login' ),
|
||||
'remove' => __( 'Remove', 'm365-login' ),
|
||||
'saveFirst' => __( 'Save the connection settings first, then search for groups.', 'm365-login' ),
|
||||
'confirmKey' => __( 'Generate a new fallback key on save? The old link stops working.', 'm365-login' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
|
@ -179,6 +198,32 @@ class M365_Login_Admin {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: search Entra groups through Microsoft Graph.
|
||||
*/
|
||||
public function ajax_search_groups() {
|
||||
check_ajax_referer( self::NONCE_TEST, 'nonce' );
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_send_json_error( array( 'message' => __( 'You are not allowed to do this.', 'm365-login' ) ), 403 );
|
||||
}
|
||||
if ( ! $this->settings->is_configured() ) {
|
||||
wp_send_json_error( array( 'message' => __( 'Save the connection settings first, then search for groups.', 'm365-login' ) ) );
|
||||
}
|
||||
|
||||
$query = isset( $_POST['query'] ) ? sanitize_text_field( wp_unslash( $_POST['query'] ) ) : '';
|
||||
$groups = $this->graph->search_groups( mb_substr( $query, 0, 100 ) );
|
||||
|
||||
if ( is_wp_error( $groups ) ) {
|
||||
$message = $groups->get_error_message();
|
||||
if ( false !== stripos( $message, 'Authorization_RequestDenied' ) || false !== stripos( $message, 'Insufficient privileges' ) ) {
|
||||
$message = __( 'Microsoft Graph refused the request. Grant the application permission "GroupMember.Read.All" (or "Directory.Read.All") with admin consent in Entra ID.', 'm365-login' );
|
||||
}
|
||||
wp_send_json_error( array( 'message' => $message ) );
|
||||
}
|
||||
|
||||
wp_send_json_success( array( 'groups' => $groups ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the settings screen.
|
||||
*/
|
||||
|
|
@ -402,6 +447,79 @@ class M365_Login_Admin {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="m365-card">
|
||||
<h2 class="m365-card__title"><?php esc_html_e( 'Allowed Entra groups (optional)', 'm365-login' ); ?></h2>
|
||||
<p class="m365-card__intro"><?php esc_html_e( 'Only members of at least one of these groups may sign in. Leave empty to allow every matched user. Nested memberships count.', 'm365-login' ); ?></p>
|
||||
|
||||
<div class="m365-field">
|
||||
<label for="m365-group-search"><?php esc_html_e( 'Search groups', 'm365-login' ); ?></label>
|
||||
<div class="m365-field__row">
|
||||
<input type="search" id="m365-group-search" class="regular-text" placeholder="<?php esc_attr_e( 'Type a group name or paste an object ID…', 'm365-login' ); ?>" autocomplete="off" <?php disabled( ! $configured ); ?> />
|
||||
<button type="button" class="button" id="m365-group-search-btn" <?php disabled( ! $configured ); ?>><?php esc_html_e( 'Search', 'm365-login' ); ?></button>
|
||||
</div>
|
||||
<?php if ( ! $configured ) : ?>
|
||||
<p class="description"><?php esc_html_e( 'Save the connection settings first, then search for groups.', 'm365-login' ); ?></p>
|
||||
<?php else : ?>
|
||||
<p class="description"><?php esc_html_e( 'Needs the application permission "GroupMember.Read.All" with admin consent. Without it you can still paste group object IDs.', 'm365-login' ); ?></p>
|
||||
<?php endif; ?>
|
||||
<div id="m365-group-results" class="m365-group-results" hidden></div>
|
||||
</div>
|
||||
|
||||
<div class="m365-field">
|
||||
<span class="m365-field__label"><?php esc_html_e( 'Selected groups', 'm365-login' ); ?></span>
|
||||
<ul id="m365-group-list" class="m365-group-list" data-empty="<?php esc_attr_e( 'No groups selected – every matched user may sign in.', 'm365-login' ); ?>">
|
||||
<?php foreach ( $this->settings->allowed_groups() as $gid => $gname ) : ?>
|
||||
<li class="m365-group-chip" data-id="<?php echo esc_attr( $gid ); ?>">
|
||||
<span class="m365-group-chip__name"><?php echo esc_html( $gname ); ?></span>
|
||||
<code class="m365-group-chip__id"><?php echo esc_html( $gid ); ?></code>
|
||||
<input type="hidden" name="<?php echo esc_attr( $option . '[allowed_groups][' . $gid . ']' ); ?>" value="<?php echo esc_attr( $gname ); ?>" />
|
||||
<button type="button" class="m365-group-chip__remove" aria-label="<?php esc_attr_e( 'Remove', 'm365-login' ); ?>">×</button>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<p class="description"><?php esc_html_e( 'Membership is read from the "groups" claim of the ID token when present; otherwise the plugin asks Microsoft Graph (application permission "User.Read.All" or "Directory.Read.All"). If neither works, the sign-in is refused.', 'm365-login' ); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="m365-card">
|
||||
<h2 class="m365-card__title"><?php esc_html_e( 'Button-only mode', 'm365-login' ); ?></h2>
|
||||
<p class="m365-card__intro"><?php esc_html_e( 'Hide the username/password form and the "Lost your password?" link, and refuse password sign-ins on the login page. Application passwords, REST and XML-RPC are not affected.', 'm365-login' ); ?></p>
|
||||
|
||||
<label class="m365-check m365-check--block">
|
||||
<input type="checkbox" name="<?php echo $field( 'button_only' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" value="1" <?php checked( $s['button_only'] ); ?> id="m365-button-only" />
|
||||
<span>
|
||||
<strong><?php esc_html_e( 'Show only the Microsoft button on the login page', 'm365-login' ); ?></strong>
|
||||
<em><?php esc_html_e( 'Becomes active once the connection is configured. Make sure your own account can sign in via Microsoft before enabling this.', 'm365-login' ); ?></em>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<div class="m365-fallback">
|
||||
<span class="m365-field__label"><?php esc_html_e( 'Fallback link (keep it secret)', 'm365-login' ); ?></span>
|
||||
<p class="description"><?php esc_html_e( 'Opening this link shows the password form again in that browser for 30 minutes. Bookmark it somewhere safe – it is your way back in if Microsoft sign-in ever breaks.', 'm365-login' ); ?></p>
|
||||
<?php if ( '' !== $this->settings->fallback_url() ) : ?>
|
||||
<div class="m365-copy">
|
||||
<code id="m365-fallback-url"><?php echo esc_html( $this->settings->fallback_url() ); ?></code>
|
||||
<button type="button" class="button button-small m365-copy__button" data-copy="m365-fallback-url"><?php esc_html_e( 'Copy', 'm365-login' ); ?></button>
|
||||
</div>
|
||||
<label class="m365-check m365-check--inline">
|
||||
<input type="checkbox" name="<?php echo $field( 'fallback_regenerate' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" value="1" id="m365-fallback-regenerate" />
|
||||
<?php esc_html_e( 'Generate a new key when saving', 'm365-login' ); ?>
|
||||
</label>
|
||||
<?php else : ?>
|
||||
<p class="m365-inline-result"><?php esc_html_e( 'A key is generated automatically the first time you save these settings.', 'm365-login' ); ?></p>
|
||||
<?php endif; ?>
|
||||
<p class="description">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: PHP constant */
|
||||
esc_html__( 'Emergency switch: add %s to wp-config.php to disable button-only mode entirely.', 'm365-login' ),
|
||||
'<code>define( \'M365_LOGIN_DISABLE_BUTTON_ONLY\', true );</code>'
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="m365-card m365-card--muted">
|
||||
<h2 class="m365-card__title"><?php esc_html_e( 'What the plugin does to keep sign-ins safe', 'm365-login' ); ?></h2>
|
||||
<ul class="m365-list">
|
||||
|
|
@ -444,6 +562,7 @@ class M365_Login_Admin {
|
|||
<li><?php esc_html_e( 'Under Token configuration add the optional claim "email" for ID tokens (recommended), then save this page.', 'm365-login' ); ?></li>
|
||||
</ol>
|
||||
<p class="description"><?php esc_html_e( 'Required API permission: openid, profile, email (delegated) – granted by default.', 'm365-login' ); ?></p>
|
||||
<p class="description"><?php esc_html_e( 'Optional, for group restrictions: application permissions GroupMember.Read.All and User.Read.All (Microsoft Graph) with admin consent.', 'm365-login' ); ?></p>
|
||||
</div>
|
||||
|
||||
<div class="m365-card m365-card--muted">
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ class M365_Login_Auth {
|
|||
const ACTION_START = 'm365_login';
|
||||
const CALLBACK_PATH = 'm365-login/callback';
|
||||
const STATE_COOKIE = 'm365_login_state';
|
||||
const FALLBACK_COOKIE = 'm365_login_fallback';
|
||||
const FALLBACK_TTL = 30 * MINUTE_IN_SECONDS;
|
||||
const STATE_TTL = 600; // 10 minutes.
|
||||
const META_OID = '_m365_login_oid';
|
||||
const META_LAST_LOGIN = '_m365_login_last_login';
|
||||
|
|
@ -28,17 +30,123 @@ class M365_Login_Auth {
|
|||
*/
|
||||
private $settings;
|
||||
|
||||
/**
|
||||
* Graph client.
|
||||
*
|
||||
* @var M365_Login_Graph
|
||||
*/
|
||||
private $graph;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param M365_Login_Settings $settings Settings.
|
||||
* @param M365_Login_Graph $graph Graph client.
|
||||
*/
|
||||
public function __construct( M365_Login_Settings $settings ) {
|
||||
public function __construct( M365_Login_Settings $settings, M365_Login_Graph $graph ) {
|
||||
$this->settings = $settings;
|
||||
$this->graph = $graph;
|
||||
|
||||
add_action( 'login_form_' . self::ACTION_START, array( $this, 'handle_start' ) );
|
||||
add_action( 'init', array( $this, 'maybe_handle_callback' ), 5 );
|
||||
add_filter( 'wp_login_errors', array( $this, 'login_errors' ), 10, 1 );
|
||||
|
||||
// Button-only mode.
|
||||
add_action( 'login_init', array( $this, 'maybe_accept_fallback_key' ) );
|
||||
// Runs after core's username/password handlers (priority 20), which would otherwise overwrite an early WP_Error.
|
||||
add_filter( 'authenticate', array( $this, 'block_password_login' ), 99, 3 );
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Button-only mode */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Whether the current browser presented the fallback key (cookie set for 30 minutes).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function fallback_active() {
|
||||
if ( ! $this->settings->button_only() ) {
|
||||
return true; // Nothing is hidden, the form is always available.
|
||||
}
|
||||
$cookie = isset( $_COOKIE[ self::FALLBACK_COOKIE ] ) ? sanitize_text_field( wp_unslash( $_COOKIE[ self::FALLBACK_COOKIE ] ) ) : '';
|
||||
return '' !== $cookie && hash_equals( $this->fallback_cookie_value(), $cookie );
|
||||
}
|
||||
|
||||
/**
|
||||
* Expected fallback cookie value (HMAC of the key, so the key itself never sits in the cookie).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function fallback_cookie_value() {
|
||||
return hash_hmac( 'sha256', 'fallback|' . $this->settings->fallback_key(), wp_salt( 'auth' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* wp-login.php?m365_fallback=KEY → sets the fallback cookie and reloads without the key in the URL.
|
||||
*/
|
||||
public function maybe_accept_fallback_key() {
|
||||
if ( ! $this->settings->button_only() ) {
|
||||
return;
|
||||
}
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- the key itself is the secret.
|
||||
$given = isset( $_GET['m365_fallback'] ) ? sanitize_text_field( wp_unslash( $_GET['m365_fallback'] ) ) : '';
|
||||
if ( '' === $given ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Slow down brute force attempts on the key.
|
||||
$ip_key = 'm365_login_fb_' . md5( $this->client_ip() );
|
||||
$attempts = (int) get_transient( $ip_key );
|
||||
if ( $attempts >= 10 ) {
|
||||
$this->fail( 'fallback_locked' );
|
||||
}
|
||||
|
||||
if ( ! hash_equals( $this->settings->fallback_key(), $given ) ) {
|
||||
set_transient( $ip_key, $attempts + 1, 15 * MINUTE_IN_SECONDS );
|
||||
$this->fail( 'fallback_invalid' );
|
||||
}
|
||||
|
||||
delete_transient( $ip_key );
|
||||
$this->send_cookie( self::FALLBACK_COOKIE, $this->fallback_cookie_value(), time() + self::FALLBACK_TTL );
|
||||
nocache_headers();
|
||||
wp_safe_redirect( add_query_arg( 'm365_fallback', 'on', wp_login_url() ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refuses username/password sign-in on wp-login.php while button-only mode is active.
|
||||
*
|
||||
* @param null|WP_User|WP_Error $user Result so far.
|
||||
* @param string $username Username.
|
||||
* @param string $password Password.
|
||||
* @return null|WP_User|WP_Error
|
||||
*/
|
||||
public function block_password_login( $user, $username, $password ) {
|
||||
if ( ! $this->settings->button_only() || $this->fallback_active() ) {
|
||||
return $user;
|
||||
}
|
||||
if ( '' === (string) $username && '' === (string) $password ) {
|
||||
return $user; // Initial form render or cookie auth, no password attempt.
|
||||
}
|
||||
// Only the interactive login form is affected: XML-RPC, REST and application passwords keep working.
|
||||
if ( ! isset( $GLOBALS['pagenow'] ) || 'wp-login.php' !== $GLOBALS['pagenow'] ) {
|
||||
return $user;
|
||||
}
|
||||
if ( ! $user instanceof WP_User ) {
|
||||
return $user; // Already failed for another reason; keep core's message.
|
||||
}
|
||||
return new WP_Error( 'm365_login_button_only', __( 'Password sign-in is disabled on this site. Please use the Microsoft button.', 'm365-login' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Best-effort client IP for rate limiting.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function client_ip() {
|
||||
return isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '0.0.0.0';
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
|
@ -276,8 +384,15 @@ class M365_Login_Auth {
|
|||
$this->fail( 'no_user' );
|
||||
}
|
||||
|
||||
// Bind the account to the immutable Microsoft object ID after first login.
|
||||
$oid = isset( $claims['oid'] ) && is_string( $claims['oid'] ) ? strtolower( $claims['oid'] ) : '';
|
||||
|
||||
// Entra group restriction.
|
||||
$group_check = $this->check_groups( $claims, $oid );
|
||||
if ( true !== $group_check ) {
|
||||
$this->fail( $group_check );
|
||||
}
|
||||
|
||||
// Bind the account to the immutable Microsoft object ID after first login.
|
||||
if ( $this->settings->get( 'bind_oid' ) ) {
|
||||
if ( '' === $oid || ! M365_Login_Settings::is_guid( $oid ) ) {
|
||||
$this->fail( 'invalid_token' );
|
||||
|
|
@ -459,6 +574,49 @@ class M365_Login_Auth {
|
|||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies membership in one of the allowed Entra groups.
|
||||
*
|
||||
* Uses the "groups" claim when the token carries one (and is not in overage),
|
||||
* otherwise asks Microsoft Graph (transitive check, needs application permissions).
|
||||
*
|
||||
* @param array $claims Verified claims.
|
||||
* @param string $oid User object ID.
|
||||
* @return true|string True, or an error code for fail().
|
||||
*/
|
||||
private function check_groups( $claims, $oid ) {
|
||||
$allowed = array_keys( $this->settings->allowed_groups() );
|
||||
if ( empty( $allowed ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$overage = ! empty( $claims['_claim_names'] ) || ! empty( $claims['hasgroups'] );
|
||||
if ( ! $overage && isset( $claims['groups'] ) && is_array( $claims['groups'] ) ) {
|
||||
$token_groups = array_map( 'strtolower', array_filter( $claims['groups'], 'is_string' ) );
|
||||
if ( array_intersect( $allowed, $token_groups ) ) {
|
||||
return true;
|
||||
}
|
||||
// The claim is authoritative when present: no need to ask Graph.
|
||||
$this->log( 'User is not a member of an allowed group (token claim).' );
|
||||
return 'not_in_group';
|
||||
}
|
||||
|
||||
if ( '' === $oid || ! M365_Login_Settings::is_guid( $oid ) ) {
|
||||
return 'invalid_token';
|
||||
}
|
||||
|
||||
$matches = $this->graph->check_member_groups( $oid, $allowed );
|
||||
if ( is_wp_error( $matches ) ) {
|
||||
$this->log( 'Group check via Microsoft Graph failed: ' . $matches->get_error_message() );
|
||||
return 'group_check_failed';
|
||||
}
|
||||
if ( empty( $matches ) ) {
|
||||
$this->log( 'User is not a member of an allowed group (Graph).' );
|
||||
return 'not_in_group';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the e-mail address used for matching.
|
||||
*
|
||||
|
|
@ -520,28 +678,29 @@ class M365_Login_Auth {
|
|||
* @param string $token Cookie value.
|
||||
*/
|
||||
private function set_state_cookie( $token ) {
|
||||
$this->send_cookie( $token, time() + self::STATE_TTL );
|
||||
$this->send_cookie( self::STATE_COOKIE, $token, time() + self::STATE_TTL );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the state cookie.
|
||||
*/
|
||||
private function clear_state_cookie() {
|
||||
$this->send_cookie( '', time() - YEAR_IN_SECONDS );
|
||||
$this->send_cookie( self::STATE_COOKIE, '', time() - YEAR_IN_SECONDS );
|
||||
}
|
||||
|
||||
/**
|
||||
* Cookie helper: HttpOnly, SameSite=Lax (needed for the top-level redirect back), Secure on HTTPS.
|
||||
*
|
||||
* @param string $name Cookie name.
|
||||
* @param string $value Value.
|
||||
* @param int $expires Expiry timestamp.
|
||||
*/
|
||||
private function send_cookie( $value, $expires ) {
|
||||
private function send_cookie( $name, $value, $expires ) {
|
||||
$path = wp_parse_url( home_url( '/' ), PHP_URL_PATH );
|
||||
$path = is_string( $path ) && '' !== $path ? $path : '/';
|
||||
|
||||
setcookie(
|
||||
self::STATE_COOKIE,
|
||||
$name,
|
||||
$value,
|
||||
array(
|
||||
'expires' => $expires,
|
||||
|
|
@ -583,8 +742,18 @@ class M365_Login_Auth {
|
|||
* @return WP_Error
|
||||
*/
|
||||
public function login_errors( $errors ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only display of a whitelisted error code.
|
||||
$code = isset( $_GET['m365_error'] ) ? sanitize_key( wp_unslash( $_GET['m365_error'] ) ) : '';
|
||||
if ( ! $errors instanceof WP_Error ) {
|
||||
$errors = new WP_Error();
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- read-only display of whitelisted flags.
|
||||
$code = isset( $_GET['m365_error'] ) ? sanitize_key( wp_unslash( $_GET['m365_error'] ) ) : '';
|
||||
$fallback_on = isset( $_GET['m365_fallback'] ) && 'on' === $_GET['m365_fallback'];
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
if ( $fallback_on && $this->settings->button_only() && $this->fallback_active() ) {
|
||||
$errors->add( 'm365_login_fallback_on', __( 'Password sign-in is temporarily enabled for this browser (30 minutes).', 'm365-login' ), 'message' );
|
||||
}
|
||||
if ( '' === $code ) {
|
||||
return $errors;
|
||||
}
|
||||
|
|
@ -601,11 +770,12 @@ class M365_Login_Auth {
|
|||
'no_user' => __( 'No WordPress account exists for your Microsoft e-mail address.', 'm365-login' ),
|
||||
'oid_mismatch' => __( 'This WordPress account is linked to a different Microsoft account. Please contact an administrator.', 'm365-login' ),
|
||||
'not_allowed' => __( 'You are not allowed to sign in with this account.', 'm365-login' ),
|
||||
'not_in_group' => __( 'Your Microsoft account is not a member of a group that is allowed to sign in here.', 'm365-login' ),
|
||||
'group_check_failed' => __( 'Your group membership could not be verified. Please contact an administrator.', 'm365-login' ),
|
||||
'fallback_invalid' => __( 'The fallback key is not valid.', 'm365-login' ),
|
||||
'fallback_locked' => __( 'Too many attempts. Please wait 15 minutes.', 'm365-login' ),
|
||||
);
|
||||
|
||||
if ( ! $errors instanceof WP_Error ) {
|
||||
$errors = new WP_Error();
|
||||
}
|
||||
$errors->add(
|
||||
'm365_login_' . $code,
|
||||
isset( $messages[ $code ] ) ? $messages[ $code ] : $messages['provider_error'],
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ class M365_Login_Button {
|
|||
$this->settings = $settings;
|
||||
|
||||
add_action( 'login_enqueue_scripts', array( $this, 'enqueue' ) );
|
||||
add_filter( 'login_body_class', array( $this, 'body_class' ) );
|
||||
add_filter( 'login_message', array( $this, 'render_above' ), 20 );
|
||||
add_action( 'login_footer', array( $this, 'render_below' ) );
|
||||
add_shortcode( 'm365_login_button', array( $this, 'shortcode' ) );
|
||||
|
|
@ -57,6 +58,28 @@ class M365_Login_Button {
|
|||
return (bool) apply_filters( 'm365_login_show_button', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the password form is hidden for this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function form_hidden() {
|
||||
return $this->should_render() && $this->settings->button_only() && ! M365_Login::instance()->auth->fallback_active();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a body class while the password form is hidden.
|
||||
*
|
||||
* @param string[] $classes Body classes.
|
||||
* @return string[]
|
||||
*/
|
||||
public function body_class( $classes ) {
|
||||
if ( $this->form_hidden() ) {
|
||||
$classes[] = 'm365-button-only';
|
||||
}
|
||||
return $classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues login styles and the small positioning script.
|
||||
*/
|
||||
|
|
@ -140,7 +163,7 @@ class M365_Login_Button {
|
|||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- passed through to the flow, validated there.
|
||||
$redirect_to = isset( $_REQUEST['redirect_to'] ) ? esc_url_raw( wp_unslash( $_REQUEST['redirect_to'] ) ) : '';
|
||||
|
||||
$divider = (string) $this->settings->get( 'divider_text' );
|
||||
$divider = $this->form_hidden() ? '' : (string) $this->settings->get( 'divider_text' );
|
||||
$divider = '' === trim( $divider ) ? '' : '<div class="m365-login__divider" aria-hidden="true"><span>' . esc_html( $divider ) . '</span></div>';
|
||||
|
||||
$html = '<div class="m365-login m365-login--' . esc_attr( $position ) . '" id="m365-login-block">';
|
||||
|
|
|
|||
240
includes/class-m365-login-graph.php
Normal file
240
includes/class-m365-login-graph.php
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
<?php
|
||||
/**
|
||||
* Minimal Microsoft Graph client (application permissions).
|
||||
*
|
||||
* @package M365_Login
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Obtains app-only tokens via client credentials and queries groups.
|
||||
*/
|
||||
class M365_Login_Graph {
|
||||
|
||||
const GRAPH_BASE = 'https://graph.microsoft.com/v1.0';
|
||||
const TOKEN_TTL = 50 * MINUTE_IN_SECONDS; // Graph tokens last ~60 minutes.
|
||||
const HTTP_TIMEOUT = 15;
|
||||
|
||||
/**
|
||||
* Settings.
|
||||
*
|
||||
* @var M365_Login_Settings
|
||||
*/
|
||||
private $settings;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param M365_Login_Settings $settings Settings.
|
||||
*/
|
||||
public function __construct( M365_Login_Settings $settings ) {
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transient key for the cached app token.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function token_cache_key() {
|
||||
return 'm365_login_apptoken_' . md5( $this->settings->tenant() . '|' . $this->settings->get( 'client_id' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops the cached app token (e.g. after the client secret changed).
|
||||
*/
|
||||
public function flush_token() {
|
||||
delete_transient( $this->token_cache_key() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an app-only access token for Microsoft Graph.
|
||||
*
|
||||
* @return string|WP_Error
|
||||
*/
|
||||
public function app_token() {
|
||||
$cached = get_transient( $this->token_cache_key() );
|
||||
if ( is_string( $cached ) && '' !== $cached ) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
if ( ! $this->settings->is_configured() ) {
|
||||
return new WP_Error( 'graph_not_configured', __( 'Microsoft login is not configured yet.', 'm365-login' ) );
|
||||
}
|
||||
|
||||
$response = wp_remote_post(
|
||||
'https://login.microsoftonline.com/' . rawurlencode( $this->settings->tenant() ) . '/oauth2/v2.0/token',
|
||||
array(
|
||||
'timeout' => self::HTTP_TIMEOUT,
|
||||
'headers' => array( 'Accept' => 'application/json' ),
|
||||
'body' => array(
|
||||
'client_id' => $this->settings->get( 'client_id' ),
|
||||
'client_secret' => $this->settings->client_secret(),
|
||||
'grant_type' => 'client_credentials',
|
||||
'scope' => 'https://graph.microsoft.com/.default',
|
||||
),
|
||||
)
|
||||
);
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
if ( 200 !== (int) wp_remote_retrieve_response_code( $response ) || empty( $body['access_token'] ) ) {
|
||||
$detail = is_array( $body ) && ! empty( $body['error_description'] ) ? (string) $body['error_description'] : 'HTTP ' . wp_remote_retrieve_response_code( $response );
|
||||
return new WP_Error( 'graph_token', $detail );
|
||||
}
|
||||
|
||||
set_transient( $this->token_cache_key(), (string) $body['access_token'], self::TOKEN_TTL );
|
||||
return (string) $body['access_token'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an authenticated Graph request.
|
||||
*
|
||||
* @param string $method HTTP method.
|
||||
* @param string $path Path relative to the v1.0 base (with query string).
|
||||
* @param array|null $json JSON body for POST requests.
|
||||
* @param array $headers Extra headers.
|
||||
* @return array|WP_Error Decoded JSON.
|
||||
*/
|
||||
private function request( $method, $path, $json = null, $headers = array() ) {
|
||||
$token = $this->app_token();
|
||||
if ( is_wp_error( $token ) ) {
|
||||
return $token;
|
||||
}
|
||||
|
||||
$args = array(
|
||||
'method' => $method,
|
||||
'timeout' => self::HTTP_TIMEOUT,
|
||||
'headers' => array_merge(
|
||||
array(
|
||||
'Authorization' => 'Bearer ' . $token,
|
||||
'Accept' => 'application/json',
|
||||
),
|
||||
$headers
|
||||
),
|
||||
);
|
||||
if ( null !== $json ) {
|
||||
$args['headers']['Content-Type'] = 'application/json';
|
||||
$args['body'] = wp_json_encode( $json );
|
||||
}
|
||||
|
||||
$response = wp_remote_request( self::GRAPH_BASE . $path, $args );
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$code = (int) wp_remote_retrieve_response_code( $response );
|
||||
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
|
||||
if ( 401 === $code ) {
|
||||
$this->flush_token();
|
||||
}
|
||||
if ( $code < 200 || $code >= 300 || ! is_array( $body ) ) {
|
||||
$graph_code = isset( $body['error']['code'] ) ? (string) $body['error']['code'] : 'HTTP ' . $code;
|
||||
$message = isset( $body['error']['message'] ) ? (string) $body['error']['message'] : '';
|
||||
return new WP_Error( 'graph_' . sanitize_key( $graph_code ), $graph_code . ( $message ? ': ' . $message : '' ) );
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches groups by display name.
|
||||
*
|
||||
* @param string $query Search text (may be empty for the first page).
|
||||
* @return array|WP_Error List of ['id' => .., 'name' => .., 'description' => ..].
|
||||
*/
|
||||
public function search_groups( $query ) {
|
||||
$query = trim( (string) $query );
|
||||
$select = '$select=id,displayName,description,securityEnabled,mailEnabled&$top=25&$orderby=displayName';
|
||||
|
||||
if ( '' !== $query && M365_Login_Settings::is_guid( $query ) ) {
|
||||
$path = '/groups/' . rawurlencode( strtolower( $query ) ) . '?$select=id,displayName,description,securityEnabled,mailEnabled';
|
||||
$item = $this->request( 'GET', $path );
|
||||
if ( is_wp_error( $item ) ) {
|
||||
return $item;
|
||||
}
|
||||
return array( $this->format_group( $item ) );
|
||||
}
|
||||
|
||||
$path = '/groups?' . $select;
|
||||
if ( '' !== $query ) {
|
||||
// $search needs the ConsistencyLevel header; the value must be wrapped in double quotes.
|
||||
$search = '"displayName:' . str_replace( '"', '', $query ) . '"';
|
||||
$path = '/groups?' . $select . '&$search=' . rawurlencode( $search ) . '&$count=true';
|
||||
}
|
||||
|
||||
$result = $this->request( 'GET', $path, null, array( 'ConsistencyLevel' => 'eventual' ) );
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$groups = array();
|
||||
if ( ! empty( $result['value'] ) && is_array( $result['value'] ) ) {
|
||||
foreach ( $result['value'] as $item ) {
|
||||
if ( is_array( $item ) && ! empty( $item['id'] ) ) {
|
||||
$groups[] = $this->format_group( $item );
|
||||
}
|
||||
}
|
||||
}
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalises a Graph group object.
|
||||
*
|
||||
* @param array $item Graph group.
|
||||
* @return array
|
||||
*/
|
||||
private function format_group( $item ) {
|
||||
$type = __( 'Group', 'm365-login' );
|
||||
if ( ! empty( $item['securityEnabled'] ) && empty( $item['mailEnabled'] ) ) {
|
||||
$type = __( 'Security group', 'm365-login' );
|
||||
} elseif ( ! empty( $item['mailEnabled'] ) ) {
|
||||
$type = __( 'Microsoft 365 group', 'm365-login' );
|
||||
}
|
||||
return array(
|
||||
'id' => strtolower( (string) $item['id'] ),
|
||||
'name' => isset( $item['displayName'] ) ? (string) $item['displayName'] : (string) $item['id'],
|
||||
'description' => isset( $item['description'] ) ? (string) $item['description'] : '',
|
||||
'type' => $type,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks (transitively) which of the given groups the user belongs to.
|
||||
*
|
||||
* @param string $user_oid User object ID.
|
||||
* @param string[] $group_ids Group object IDs (any count; chunked by 20).
|
||||
* @return string[]|WP_Error Matching group IDs.
|
||||
*/
|
||||
public function check_member_groups( $user_oid, $group_ids ) {
|
||||
if ( ! M365_Login_Settings::is_guid( $user_oid ) ) {
|
||||
return new WP_Error( 'graph_bad_oid', 'Invalid user object ID.' );
|
||||
}
|
||||
|
||||
$matches = array();
|
||||
foreach ( array_chunk( array_values( $group_ids ), 20 ) as $chunk ) {
|
||||
$result = $this->request(
|
||||
'POST',
|
||||
'/users/' . rawurlencode( strtolower( $user_oid ) ) . '/checkMemberGroups',
|
||||
array( 'groupIds' => $chunk )
|
||||
);
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
if ( ! empty( $result['value'] ) && is_array( $result['value'] ) ) {
|
||||
foreach ( $result['value'] as $id ) {
|
||||
$matches[] = strtolower( (string) $id );
|
||||
}
|
||||
}
|
||||
if ( ! empty( $matches ) ) {
|
||||
break; // One match is enough.
|
||||
}
|
||||
}
|
||||
return $matches;
|
||||
}
|
||||
}
|
||||
|
|
@ -35,7 +35,11 @@ class M365_Login_Settings {
|
|||
'upn_fallback' => 1,
|
||||
'bind_oid' => 1,
|
||||
'allowed_domains' => '',
|
||||
'allowed_groups' => array(), // id => display name.
|
||||
'remember_me' => 0,
|
||||
// Button-only mode.
|
||||
'button_only' => 0,
|
||||
'fallback_key' => '',
|
||||
// Button appearance.
|
||||
'button_text' => __( 'Sign in with Microsoft', 'm365-login' ),
|
||||
'button_icon' => '', // Empty = bundled Microsoft logo.
|
||||
|
|
@ -157,6 +161,71 @@ class M365_Login_Settings {
|
|||
return array_values( array_unique( $out ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Allowed Entra group IDs (lowercase GUIDs) mapped to display names.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function allowed_groups() {
|
||||
$raw = $this->get( 'allowed_groups', array() );
|
||||
$out = array();
|
||||
if ( is_array( $raw ) ) {
|
||||
foreach ( $raw as $id => $name ) {
|
||||
$id = strtolower( (string) $id );
|
||||
if ( self::is_guid( $id ) ) {
|
||||
$out[ $id ] = (string) $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the password form is hidden and password sign-in blocked.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function button_only() {
|
||||
if ( defined( 'M365_LOGIN_DISABLE_BUTTON_ONLY' ) && M365_LOGIN_DISABLE_BUTTON_ONLY ) {
|
||||
return false;
|
||||
}
|
||||
return $this->is_configured() && (bool) $this->get( 'button_only' ) && '' !== $this->fallback_key();
|
||||
}
|
||||
|
||||
/**
|
||||
* Secret key that re-enables the password form.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function fallback_key() {
|
||||
$key = (string) $this->get( 'fallback_key', '' );
|
||||
return preg_match( '/^[A-Za-z0-9]{16,64}$/', $key ) ? $key : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* URL that shows the password form again when button-only mode is active.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function fallback_url() {
|
||||
$key = $this->fallback_key();
|
||||
return '' === $key ? '' : add_query_arg( 'm365_fallback', $key, wp_login_url() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new fallback key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function generate_fallback_key() {
|
||||
$alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789';
|
||||
$key = '';
|
||||
for ( $i = 0; $i < 24; $i++ ) {
|
||||
$key .= $alphabet[ random_int( 0, strlen( $alphabet ) - 1 ) ];
|
||||
}
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitises settings coming from the admin form.
|
||||
*
|
||||
|
|
@ -215,6 +284,31 @@ class M365_Login_Settings {
|
|||
$domains = preg_replace( '/[^a-z0-9.\-@,;\s]/i', '', $domains );
|
||||
$out['allowed_domains'] = trim( (string) $domains );
|
||||
|
||||
// Allowed groups: GUID => name.
|
||||
$groups = array();
|
||||
if ( ! empty( $input['allowed_groups'] ) && is_array( $input['allowed_groups'] ) ) {
|
||||
foreach ( $input['allowed_groups'] as $id => $name ) {
|
||||
$id = strtolower( trim( sanitize_text_field( wp_unslash( (string) $id ) ) ) );
|
||||
if ( ! self::is_guid( $id ) ) {
|
||||
continue;
|
||||
}
|
||||
$name = sanitize_text_field( wp_unslash( (string) $name ) );
|
||||
$groups[ $id ] = '' === $name ? $id : mb_substr( $name, 0, 120 );
|
||||
if ( count( $groups ) >= 100 ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$out['allowed_groups'] = $groups;
|
||||
|
||||
// Button-only mode + fallback key.
|
||||
$out['button_only'] = empty( $input['button_only'] ) ? 0 : 1;
|
||||
$key = (string) $current['fallback_key'];
|
||||
if ( ! empty( $input['fallback_regenerate'] ) || ! preg_match( '/^[A-Za-z0-9]{16,64}$/', $key ) ) {
|
||||
$key = self::generate_fallback_key();
|
||||
}
|
||||
$out['fallback_key'] = $key;
|
||||
|
||||
// Button.
|
||||
$text = isset( $input['button_text'] ) ? sanitize_text_field( wp_unslash( $input['button_text'] ) ) : '';
|
||||
$out['button_text'] = '' === trim( $text ) ? $defaults['button_text'] : mb_substr( $text, 0, 80 );
|
||||
|
|
|
|||
|
|
@ -33,6 +33,13 @@ final class M365_Login {
|
|||
*/
|
||||
public $auth;
|
||||
|
||||
/**
|
||||
* Microsoft Graph client.
|
||||
*
|
||||
* @var M365_Login_Graph
|
||||
*/
|
||||
public $graph;
|
||||
|
||||
/**
|
||||
* Login button component.
|
||||
*
|
||||
|
|
@ -66,11 +73,12 @@ final class M365_Login {
|
|||
add_action( 'init', array( $this, 'load_textdomain' ) );
|
||||
|
||||
$this->settings = new M365_Login_Settings();
|
||||
$this->auth = new M365_Login_Auth( $this->settings );
|
||||
$this->graph = new M365_Login_Graph( $this->settings );
|
||||
$this->auth = new M365_Login_Auth( $this->settings, $this->graph );
|
||||
$this->button = new M365_Login_Button( $this->settings );
|
||||
|
||||
if ( is_admin() ) {
|
||||
$this->admin = new M365_Login_Admin( $this->settings, $this->auth );
|
||||
$this->admin = new M365_Login_Admin( $this->settings, $this->auth, $this->graph );
|
||||
}
|
||||
|
||||
add_filter( 'plugin_action_links_' . plugin_basename( M365_LOGIN_FILE ), array( $this, 'action_links' ) );
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -16,445 +16,587 @@ msgstr ""
|
|||
"X-Generator: bin/make-pot.py\n"
|
||||
"X-Domain: m365-login\n"
|
||||
|
||||
#: includes/class-m365-login-admin.php:63 includes/class-m365-login-admin.php:64 includes/class-m365-login-admin.php:203
|
||||
#: includes/class-m365-login-admin.php:75 includes/class-m365-login-admin.php:76 includes/class-m365-login-admin.php:248
|
||||
msgid "M365 Login"
|
||||
msgstr "M365 Login"
|
||||
|
||||
#: includes/class-m365-login-admin.php:102
|
||||
#: includes/class-m365-login-admin.php:114
|
||||
msgid "M365 Login is active but not connected to Microsoft Entra ID yet."
|
||||
msgstr "M365 Login ist aktiv, aber noch nicht mit Microsoft Entra ID verbunden."
|
||||
|
||||
#: includes/class-m365-login-admin.php:104
|
||||
#: includes/class-m365-login-admin.php:116
|
||||
msgid "Open the settings"
|
||||
msgstr "Einstellungen öffnen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:132
|
||||
#: includes/class-m365-login-admin.php:145
|
||||
msgid "Choose button icon"
|
||||
msgstr "Button-Icon auswählen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:133
|
||||
#: includes/class-m365-login-admin.php:146
|
||||
msgid "Use this icon"
|
||||
msgstr "Dieses Icon verwenden"
|
||||
|
||||
#: includes/class-m365-login-admin.php:134
|
||||
#: includes/class-m365-login-admin.php:147
|
||||
msgid "Copied!"
|
||||
msgstr "Kopiert!"
|
||||
|
||||
#: includes/class-m365-login-admin.php:135 includes/class-m365-login-admin.php:427
|
||||
#: includes/class-m365-login-admin.php:148 includes/class-m365-login-admin.php:502 includes/class-m365-login-admin.php:545
|
||||
msgid "Copy"
|
||||
msgstr "Kopieren"
|
||||
|
||||
#: includes/class-m365-login-admin.php:136
|
||||
#: includes/class-m365-login-admin.php:149
|
||||
msgid "Testing…"
|
||||
msgstr "Wird geprüft …"
|
||||
|
||||
#: includes/class-m365-login-admin.php:137
|
||||
#: includes/class-m365-login-admin.php:150
|
||||
msgid "The tenant could not be reached. Check the tenant ID and the server’s outgoing connections."
|
||||
msgstr "Der Tenant ist nicht erreichbar. Bitte Tenant-ID und ausgehende Verbindungen des Servers prüfen."
|
||||
|
||||
#: includes/class-m365-login-admin.php:149
|
||||
#: includes/class-m365-login-admin.php:151
|
||||
msgid "No groups found."
|
||||
msgstr "Keine Gruppen gefunden."
|
||||
|
||||
#: includes/class-m365-login-admin.php:152
|
||||
msgid "Searching…"
|
||||
msgstr "Suche läuft …"
|
||||
|
||||
#: includes/class-m365-login-admin.php:153
|
||||
msgid "Add"
|
||||
msgstr "Hinzufügen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:154 includes/class-m365-login-admin.php:476
|
||||
msgid "Remove"
|
||||
msgstr "Entfernen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:155 includes/class-m365-login-admin.php:210 includes/class-m365-login-admin.php:461
|
||||
msgid "Save the connection settings first, then search for groups."
|
||||
msgstr "Zuerst die Verbindungseinstellungen speichern, dann Gruppen suchen."
|
||||
|
||||
#: includes/class-m365-login-admin.php:156
|
||||
msgid "Generate a new fallback key on save? The old link stops working."
|
||||
msgstr "Beim Speichern einen neuen Fallback-Schlüssel erzeugen? Der alte Link funktioniert dann nicht mehr."
|
||||
|
||||
#: includes/class-m365-login-admin.php:168 includes/class-m365-login-admin.php:207
|
||||
msgid "You are not allowed to do this."
|
||||
msgstr "Dafür fehlt die Berechtigung."
|
||||
|
||||
#: includes/class-m365-login-admin.php:154
|
||||
#: includes/class-m365-login-admin.php:173
|
||||
msgid "Please enter a valid tenant ID first."
|
||||
msgstr "Bitte zuerst eine gültige Tenant-ID eingeben."
|
||||
|
||||
#. translators: %d: HTTP status code
|
||||
#: includes/class-m365-login-admin.php:168
|
||||
#: includes/class-m365-login-admin.php:187
|
||||
msgid "Microsoft answered with HTTP %d. Is the tenant ID correct?"
|
||||
msgstr "Microsoft hat mit HTTP %d geantwortet. Ist die Tenant-ID korrekt?"
|
||||
|
||||
#. translators: %d: HTTP status code
|
||||
#: includes/class-m365-login-admin.php:177
|
||||
#: includes/class-m365-login-admin.php:196
|
||||
msgid "Tenant reachable. The OpenID configuration was loaded successfully."
|
||||
msgstr "Tenant erreichbar. Die OpenID-Konfiguration wurde erfolgreich geladen."
|
||||
|
||||
#: includes/class-m365-login-admin.php:187
|
||||
#: includes/class-m365-login-admin.php:219
|
||||
msgid "Microsoft Graph refused the request. Grant the application permission \"GroupMember.Read.All\" (or \"Directory.Read.All\") with admin consent in Entra ID."
|
||||
msgstr "Microsoft Graph hat die Anfrage abgelehnt. In Entra ID die Anwendungsberechtigung „GroupMember.Read.All“ (oder „Directory.Read.All“) mit Administratorzustimmung erteilen."
|
||||
|
||||
#: includes/class-m365-login-admin.php:232
|
||||
msgid "You are not allowed to access this page."
|
||||
msgstr "Für diese Seite fehlt die Berechtigung."
|
||||
|
||||
#: includes/class-m365-login-admin.php:204
|
||||
#: includes/class-m365-login-admin.php:249
|
||||
msgid "Let existing users sign in with their Microsoft 365 / Entra ID account."
|
||||
msgstr "Bestehende Benutzer melden sich mit ihrem Microsoft 365 / Entra ID-Konto an."
|
||||
|
||||
#: includes/class-m365-login-admin.php:209
|
||||
#: includes/class-m365-login-admin.php:254
|
||||
msgid "Connected"
|
||||
msgstr "Verbunden"
|
||||
|
||||
#: includes/class-m365-login-admin.php:209
|
||||
#: includes/class-m365-login-admin.php:254
|
||||
msgid "Setup incomplete"
|
||||
msgstr "Einrichtung unvollständig"
|
||||
|
||||
#: includes/class-m365-login-admin.php:217
|
||||
#: includes/class-m365-login-admin.php:262
|
||||
msgid "Connection"
|
||||
msgstr "Verbindung"
|
||||
|
||||
#: includes/class-m365-login-admin.php:218
|
||||
#: includes/class-m365-login-admin.php:263
|
||||
msgid "Button"
|
||||
msgstr "Button"
|
||||
|
||||
#: includes/class-m365-login-admin.php:219
|
||||
#: includes/class-m365-login-admin.php:264
|
||||
msgid "Security"
|
||||
msgstr "Sicherheit"
|
||||
|
||||
#: includes/class-m365-login-admin.php:228
|
||||
#: includes/class-m365-login-admin.php:273
|
||||
msgid "Microsoft Entra ID app registration"
|
||||
msgstr "App-Registrierung in Microsoft Entra ID"
|
||||
|
||||
#: includes/class-m365-login-admin.php:229
|
||||
#: includes/class-m365-login-admin.php:274
|
||||
msgid "Enter the values from your app registration in the Microsoft Entra admin center."
|
||||
msgstr "Trage hier die Werte aus deiner App-Registrierung im Microsoft Entra Admin Center ein."
|
||||
|
||||
#: includes/class-m365-login-admin.php:232
|
||||
#: includes/class-m365-login-admin.php:277
|
||||
msgid "Directory (tenant) ID"
|
||||
msgstr "Verzeichnis-ID (Mandant/Tenant)"
|
||||
|
||||
#: includes/class-m365-login-admin.php:235
|
||||
#: includes/class-m365-login-admin.php:280
|
||||
msgid "Test tenant"
|
||||
msgstr "Tenant testen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:237
|
||||
#: includes/class-m365-login-admin.php:282
|
||||
msgid "Recommended: the GUID of your tenant. Only sign-ins from this tenant are accepted. \"organizations\" allows any work or school account."
|
||||
msgstr "Empfohlen: die GUID deines Tenants. Dann werden nur Anmeldungen aus diesem Tenant akzeptiert. „organizations“ erlaubt beliebige Geschäfts-, Schul- oder Unikonten."
|
||||
|
||||
#: includes/class-m365-login-admin.php:242
|
||||
#: includes/class-m365-login-admin.php:287
|
||||
msgid "Application (client) ID"
|
||||
msgstr "Anwendungs-ID (Client)"
|
||||
|
||||
#: includes/class-m365-login-admin.php:247
|
||||
#: includes/class-m365-login-admin.php:292
|
||||
msgid "Client secret"
|
||||
msgstr "Geheimer Clientschlüssel (Client Secret)"
|
||||
|
||||
#: includes/class-m365-login-admin.php:249
|
||||
#: includes/class-m365-login-admin.php:294
|
||||
msgid "•••••••••••• (stored, leave empty to keep)"
|
||||
msgstr "•••••••••••• (gespeichert – leer lassen, um zu behalten)"
|
||||
|
||||
#: includes/class-m365-login-admin.php:249
|
||||
#: includes/class-m365-login-admin.php:294
|
||||
msgid "Paste the secret value"
|
||||
msgstr "Wert des Secrets einfügen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:250
|
||||
#: includes/class-m365-login-admin.php:295
|
||||
msgid "Show secret"
|
||||
msgstr "Secret anzeigen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:255
|
||||
#: includes/class-m365-login-admin.php:300
|
||||
msgid "Remove the stored secret"
|
||||
msgstr "Gespeichertes Secret entfernen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:258
|
||||
#: includes/class-m365-login-admin.php:303
|
||||
msgid "Stored encrypted (AES-256-GCM, key derived from your WordPress salts) and never displayed again. Client secrets expire – note the expiry date in Entra ID."
|
||||
msgstr "Wird verschlüsselt gespeichert (AES-256-GCM, Schlüssel aus den WordPress-Salts abgeleitet) und nie wieder angezeigt. Client Secrets laufen ab – Ablaufdatum in Entra ID notieren."
|
||||
|
||||
#: includes/class-m365-login-admin.php:262
|
||||
#: includes/class-m365-login-admin.php:307
|
||||
msgid "Account prompt"
|
||||
msgstr "Kontoauswahl"
|
||||
|
||||
#: includes/class-m365-login-admin.php:264
|
||||
#: includes/class-m365-login-admin.php:309
|
||||
msgid "Always let the user pick an account (recommended)"
|
||||
msgstr "Benutzer wählt immer ein Konto aus (empfohlen)"
|
||||
|
||||
#: includes/class-m365-login-admin.php:265
|
||||
#: includes/class-m365-login-admin.php:310
|
||||
msgid "Use the current Microsoft session if available"
|
||||
msgstr "Vorhandene Microsoft-Sitzung verwenden, falls vorhanden"
|
||||
|
||||
#: includes/class-m365-login-admin.php:266
|
||||
#: includes/class-m365-login-admin.php:311
|
||||
msgid "Always require re-entering credentials"
|
||||
msgstr "Immer erneute Eingabe der Anmeldedaten verlangen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:275
|
||||
#: includes/class-m365-login-admin.php:320
|
||||
msgid "Appearance"
|
||||
msgstr "Darstellung"
|
||||
|
||||
#: includes/class-m365-login-admin.php:278
|
||||
#: includes/class-m365-login-admin.php:323
|
||||
msgid "Live preview"
|
||||
msgstr "Live-Vorschau"
|
||||
|
||||
#: includes/class-m365-login-admin.php:292
|
||||
#: includes/class-m365-login-admin.php:337
|
||||
msgid "Button text"
|
||||
msgstr "Button-Text"
|
||||
|
||||
#: includes/class-m365-login-admin.php:296
|
||||
#: includes/class-m365-login-admin.php:341
|
||||
msgid "Divider text"
|
||||
msgstr "Trennlinien-Text"
|
||||
|
||||
#: includes/class-m365-login-admin.php:298
|
||||
#: includes/class-m365-login-admin.php:343
|
||||
msgid "Leave empty to hide the divider line."
|
||||
msgstr "Leer lassen, um die Trennlinie auszublenden."
|
||||
|
||||
#: includes/class-m365-login-admin.php:303
|
||||
#: includes/class-m365-login-admin.php:348
|
||||
msgid "Icon"
|
||||
msgstr "Icon"
|
||||
|
||||
#: includes/class-m365-login-admin.php:306
|
||||
#: includes/class-m365-login-admin.php:351
|
||||
msgid "Show an icon on the button"
|
||||
msgstr "Icon auf dem Button anzeigen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:317
|
||||
#: includes/class-m365-login-admin.php:362
|
||||
msgid "Default: Microsoft logo"
|
||||
msgstr "Standard: Microsoft-Logo"
|
||||
|
||||
#: includes/class-m365-login-admin.php:319
|
||||
#: includes/class-m365-login-admin.php:364
|
||||
msgid "Choose from media library"
|
||||
msgstr "Aus Mediathek wählen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:320
|
||||
#: includes/class-m365-login-admin.php:365
|
||||
msgid "Use Microsoft logo"
|
||||
msgstr "Microsoft-Logo verwenden"
|
||||
|
||||
#: includes/class-m365-login-admin.php:322
|
||||
#: includes/class-m365-login-admin.php:367
|
||||
msgid "PNG, SVG, JPG or WebP. Square images (e.g. 64×64 px) work best."
|
||||
msgstr "PNG, SVG, JPG oder WebP. Quadratische Bilder (z. B. 64×64 px) eignen sich am besten."
|
||||
|
||||
#: includes/class-m365-login-admin.php:330
|
||||
#: includes/class-m365-login-admin.php:375
|
||||
msgid "Background"
|
||||
msgstr "Hintergrund"
|
||||
|
||||
#: includes/class-m365-login-admin.php:331
|
||||
#: includes/class-m365-login-admin.php:376
|
||||
msgid "Background (hover)"
|
||||
msgstr "Hintergrund (Hover)"
|
||||
|
||||
#: includes/class-m365-login-admin.php:332
|
||||
#: includes/class-m365-login-admin.php:377
|
||||
msgid "Text colour"
|
||||
msgstr "Textfarbe"
|
||||
|
||||
#: includes/class-m365-login-admin.php:333
|
||||
#: includes/class-m365-login-admin.php:378
|
||||
msgid "Border"
|
||||
msgstr "Rahmen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:346
|
||||
#: includes/class-m365-login-admin.php:391
|
||||
msgid "Corner radius"
|
||||
msgstr "Eckenradius"
|
||||
|
||||
#: includes/class-m365-login-admin.php:350
|
||||
#: includes/class-m365-login-admin.php:395
|
||||
msgid "Position on the login page"
|
||||
msgstr "Position auf der Login-Seite"
|
||||
|
||||
#: includes/class-m365-login-admin.php:352
|
||||
#: includes/class-m365-login-admin.php:397
|
||||
msgid "Below the login form"
|
||||
msgstr "Unter dem Login-Formular"
|
||||
|
||||
#: includes/class-m365-login-admin.php:353
|
||||
#: includes/class-m365-login-admin.php:398
|
||||
msgid "Above the login form"
|
||||
msgstr "Über dem Login-Formular"
|
||||
|
||||
#: includes/class-m365-login-admin.php:359
|
||||
#: includes/class-m365-login-admin.php:404
|
||||
msgid "Quick presets"
|
||||
msgstr "Schnellauswahl"
|
||||
|
||||
#: includes/class-m365-login-admin.php:360
|
||||
#: includes/class-m365-login-admin.php:405
|
||||
msgid "Microsoft dark"
|
||||
msgstr "Microsoft dunkel"
|
||||
|
||||
#: includes/class-m365-login-admin.php:361
|
||||
#: includes/class-m365-login-admin.php:406
|
||||
msgid "Microsoft light"
|
||||
msgstr "Microsoft hell"
|
||||
|
||||
#: includes/class-m365-login-admin.php:362
|
||||
#: includes/class-m365-login-admin.php:407
|
||||
msgid "Azure blue"
|
||||
msgstr "Azure-Blau"
|
||||
|
||||
#: includes/class-m365-login-admin.php:363
|
||||
#: includes/class-m365-login-admin.php:408
|
||||
msgid "WordPress blue"
|
||||
msgstr "WordPress-Blau"
|
||||
|
||||
#: includes/class-m365-login-admin.php:371
|
||||
#: includes/class-m365-login-admin.php:416
|
||||
msgid "User matching & hardening"
|
||||
msgstr "Benutzerzuordnung & Härtung"
|
||||
|
||||
#: includes/class-m365-login-admin.php:372
|
||||
#: includes/class-m365-login-admin.php:417
|
||||
msgid "Users are never created automatically. A Microsoft sign-in only succeeds when a WordPress user with the same e-mail address already exists."
|
||||
msgstr "Benutzer werden nie automatisch angelegt. Eine Microsoft-Anmeldung gelingt nur, wenn bereits ein WordPress-Benutzer mit derselben E-Mail-Adresse existiert."
|
||||
|
||||
#: includes/class-m365-login-admin.php:377
|
||||
#: includes/class-m365-login-admin.php:422
|
||||
msgid "Bind WordPress accounts to the Microsoft object ID"
|
||||
msgstr "WordPress-Konten an die Microsoft-Objekt-ID binden"
|
||||
|
||||
#: includes/class-m365-login-admin.php:378
|
||||
#: includes/class-m365-login-admin.php:423
|
||||
msgid "On first sign-in the immutable Microsoft object ID is stored with the user. Later sign-ins with the same e-mail but a different Microsoft identity are rejected. Strongly recommended."
|
||||
msgstr "Bei der ersten Anmeldung wird die unveränderliche Microsoft-Objekt-ID am Benutzer gespeichert. Spätere Anmeldungen mit gleicher E-Mail, aber anderer Microsoft-Identität werden abgelehnt. Dringend empfohlen."
|
||||
|
||||
#: includes/class-m365-login-admin.php:385
|
||||
#: includes/class-m365-login-admin.php:430
|
||||
msgid "Fall back to the user principal name (UPN)"
|
||||
msgstr "Auf den User Principal Name (UPN) zurückgreifen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:386
|
||||
#: includes/class-m365-login-admin.php:431
|
||||
msgid "If the token contains no \"email\" claim, use the UPN (e.g. jane@contoso.com) when it is a valid e-mail address. Usually required for work accounts."
|
||||
msgstr "Enthält das Token keinen „email“-Claim, wird der UPN (z. B. jane@contoso.com) verwendet, sofern er eine gültige E-Mail-Adresse ist. Für Geschäftskonten meist erforderlich."
|
||||
|
||||
#: includes/class-m365-login-admin.php:393
|
||||
#: includes/class-m365-login-admin.php:438
|
||||
msgid "Keep users signed in (\"Remember me\")"
|
||||
msgstr "Benutzer angemeldet lassen („Angemeldet bleiben“)"
|
||||
|
||||
#: includes/class-m365-login-admin.php:394
|
||||
#: includes/class-m365-login-admin.php:439
|
||||
msgid "Issues a 14-day WordPress session instead of a browser session."
|
||||
msgstr "Erstellt eine 14-tägige WordPress-Sitzung statt einer Browser-Sitzung."
|
||||
|
||||
#: includes/class-m365-login-admin.php:399
|
||||
#: includes/class-m365-login-admin.php:444
|
||||
msgid "Allowed e-mail domains (optional)"
|
||||
msgstr "Erlaubte E-Mail-Domains (optional)"
|
||||
|
||||
#: includes/class-m365-login-admin.php:401
|
||||
#: includes/class-m365-login-admin.php:446
|
||||
msgid "One or more domains separated by commas or new lines. Leave empty to allow any domain of your tenant."
|
||||
msgstr "Eine oder mehrere Domains, getrennt durch Kommas oder Zeilenumbrüche. Leer lassen, um alle Domains des Tenants zuzulassen."
|
||||
|
||||
#: includes/class-m365-login-admin.php:406
|
||||
#: includes/class-m365-login-admin.php:451
|
||||
msgid "Allowed Entra groups (optional)"
|
||||
msgstr "Erlaubte Entra-Gruppen (optional)"
|
||||
|
||||
#: includes/class-m365-login-admin.php:452
|
||||
msgid "Only members of at least one of these groups may sign in. Leave empty to allow every matched user. Nested memberships count."
|
||||
msgstr "Nur Mitglieder mindestens einer dieser Gruppen dürfen sich anmelden. Leer lassen, um alle zugeordneten Benutzer zuzulassen. Verschachtelte Mitgliedschaften zählen."
|
||||
|
||||
#: includes/class-m365-login-admin.php:455
|
||||
msgid "Search groups"
|
||||
msgstr "Gruppen suchen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:457
|
||||
msgid "Type a group name or paste an object ID…"
|
||||
msgstr "Gruppenname eingeben oder Objekt-ID einfügen …"
|
||||
|
||||
#: includes/class-m365-login-admin.php:458
|
||||
msgid "Search"
|
||||
msgstr "Suchen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:463
|
||||
msgid "Needs the application permission \"GroupMember.Read.All\" with admin consent. Without it you can still paste group object IDs."
|
||||
msgstr "Benötigt die Anwendungsberechtigung „GroupMember.Read.All“ mit Administratorzustimmung. Ohne sie können Gruppen-Objekt-IDs trotzdem eingefügt werden."
|
||||
|
||||
#: includes/class-m365-login-admin.php:469
|
||||
msgid "Selected groups"
|
||||
msgstr "Ausgewählte Gruppen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:470
|
||||
msgid "No groups selected – every matched user may sign in."
|
||||
msgstr "Keine Gruppen ausgewählt – jeder zugeordnete Benutzer darf sich anmelden."
|
||||
|
||||
#: includes/class-m365-login-admin.php:480
|
||||
msgid "Membership is read from the \"groups\" claim of the ID token when present; otherwise the plugin asks Microsoft Graph (application permission \"User.Read.All\" or \"Directory.Read.All\"). If neither works, the sign-in is refused."
|
||||
msgstr "Die Mitgliedschaft wird aus dem „groups“-Claim des ID-Tokens gelesen, falls vorhanden; andernfalls fragt das Plugin Microsoft Graph (Anwendungsberechtigung „User.Read.All“ oder „Directory.Read.All“). Funktioniert beides nicht, wird die Anmeldung abgelehnt."
|
||||
|
||||
#: includes/class-m365-login-admin.php:485
|
||||
msgid "Button-only mode"
|
||||
msgstr "Nur-Button-Modus"
|
||||
|
||||
#: includes/class-m365-login-admin.php:486
|
||||
msgid "Hide the username/password form and the \"Lost your password?\" link, and refuse password sign-ins on the login page. Application passwords, REST and XML-RPC are not affected."
|
||||
msgstr "Blendet das Benutzername/Passwort-Formular und den Link „Passwort vergessen?“ aus und lehnt Passwort-Anmeldungen auf der Login-Seite ab. Anwendungspasswörter, REST und XML-RPC sind nicht betroffen."
|
||||
|
||||
#: includes/class-m365-login-admin.php:491
|
||||
msgid "Show only the Microsoft button on the login page"
|
||||
msgstr "Auf der Login-Seite nur den Microsoft-Button anzeigen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:492
|
||||
msgid "Becomes active once the connection is configured. Make sure your own account can sign in via Microsoft before enabling this."
|
||||
msgstr "Wird aktiv, sobald die Verbindung eingerichtet ist. Vor dem Aktivieren sicherstellen, dass das eigene Konto sich per Microsoft anmelden kann."
|
||||
|
||||
#: includes/class-m365-login-admin.php:497
|
||||
msgid "Fallback link (keep it secret)"
|
||||
msgstr "Fallback-Link (geheim halten)"
|
||||
|
||||
#: includes/class-m365-login-admin.php:498
|
||||
msgid "Opening this link shows the password form again in that browser for 30 minutes. Bookmark it somewhere safe – it is your way back in if Microsoft sign-in ever breaks."
|
||||
msgstr "Wer diesen Link öffnet, sieht in diesem Browser 30 Minuten lang wieder das Passwort-Formular. Sicher aufbewahren – er ist der Weg zurück, falls die Microsoft-Anmeldung einmal nicht funktioniert."
|
||||
|
||||
#: includes/class-m365-login-admin.php:506
|
||||
msgid "Generate a new key when saving"
|
||||
msgstr "Beim Speichern einen neuen Schlüssel erzeugen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:509
|
||||
msgid "A key is generated automatically the first time you save these settings."
|
||||
msgstr "Beim ersten Speichern dieser Einstellungen wird automatisch ein Schlüssel erzeugt."
|
||||
|
||||
#. translators: %s: PHP constant
|
||||
#: includes/class-m365-login-admin.php:515
|
||||
msgid "Emergency switch: add %s to wp-config.php to disable button-only mode entirely."
|
||||
msgstr "Notschalter: %s in die wp-config.php eintragen, um den Nur-Button-Modus vollständig abzuschalten."
|
||||
|
||||
#. translators: %s: PHP constant
|
||||
#: includes/class-m365-login-admin.php:524
|
||||
msgid "What the plugin does to keep sign-ins safe"
|
||||
msgstr "So schützt das Plugin die Anmeldung"
|
||||
|
||||
#: includes/class-m365-login-admin.php:408
|
||||
#: includes/class-m365-login-admin.php:526
|
||||
msgid "OpenID Connect authorization code flow with PKCE (S256) – no tokens ever pass through the browser."
|
||||
msgstr "OpenID Connect Authorization Code Flow mit PKCE (S256) – Tokens laufen nie durch den Browser."
|
||||
|
||||
#: includes/class-m365-login-admin.php:409
|
||||
#: includes/class-m365-login-admin.php:527
|
||||
msgid "Single-use state and nonce values bound to the browser via an HttpOnly cookie (CSRF and replay protection)."
|
||||
msgstr "Einmalige State- und Nonce-Werte, per HttpOnly-Cookie an den Browser gebunden (CSRF- und Replay-Schutz)."
|
||||
|
||||
#: includes/class-m365-login-admin.php:410
|
||||
#: includes/class-m365-login-admin.php:528
|
||||
msgid "ID token signature verified against Microsoft’s published signing keys; issuer, audience, tenant, expiry and nonce are checked."
|
||||
msgstr "Signatur des ID-Tokens wird gegen Microsofts veröffentlichte Signaturschlüssel geprüft; Issuer, Audience, Tenant, Ablauf und Nonce werden kontrolliert."
|
||||
|
||||
#: includes/class-m365-login-admin.php:411
|
||||
#: includes/class-m365-login-admin.php:529
|
||||
msgid "Client secret encrypted at rest; no accounts are created, no passwords are changed."
|
||||
msgstr "Client Secret verschlüsselt gespeichert; es werden keine Konten angelegt und keine Passwörter geändert."
|
||||
|
||||
#: includes/class-m365-login-admin.php:417
|
||||
#: includes/class-m365-login-admin.php:535
|
||||
msgid "Save changes"
|
||||
msgstr "Änderungen speichern"
|
||||
|
||||
#: includes/class-m365-login-admin.php:423
|
||||
#: includes/class-m365-login-admin.php:541
|
||||
msgid "Redirect URI"
|
||||
msgstr "Umleitungs-URI (Redirect URI)"
|
||||
|
||||
#: includes/class-m365-login-admin.php:424
|
||||
#: includes/class-m365-login-admin.php:542
|
||||
msgid "Register this URI in your app registration under Authentication → Web → Redirect URIs:"
|
||||
msgstr "Diese URI in der App-Registrierung unter Authentifizierung → Web → Umleitungs-URIs eintragen:"
|
||||
|
||||
#: includes/class-m365-login-admin.php:430
|
||||
#: includes/class-m365-login-admin.php:548
|
||||
msgid "Plain permalinks are active, so the callback uses a query string. If you enable pretty permalinks later, the redirect URI changes and must be updated in Entra ID."
|
||||
msgstr "Einfache Permalinks sind aktiv, daher verwendet der Callback einen Query-String. Werden später sprechende Permalinks aktiviert, ändert sich die Umleitungs-URI und muss in Entra ID angepasst werden."
|
||||
|
||||
#: includes/class-m365-login-admin.php:433
|
||||
#: includes/class-m365-login-admin.php:551
|
||||
msgid "Your site does not use HTTPS. Microsoft only accepts http:// redirect URIs for localhost; production sites must use HTTPS."
|
||||
msgstr "Diese Website nutzt kein HTTPS. Microsoft akzeptiert http://-Umleitungs-URIs nur für localhost; produktive Websites benötigen HTTPS."
|
||||
|
||||
#: includes/class-m365-login-admin.php:438
|
||||
#: includes/class-m365-login-admin.php:556
|
||||
msgid "Setup in 5 steps"
|
||||
msgstr "Einrichtung in 5 Schritten"
|
||||
|
||||
#: includes/class-m365-login-admin.php:440
|
||||
#: includes/class-m365-login-admin.php:558
|
||||
msgid "Open the Microsoft Entra admin center → App registrations → New registration."
|
||||
msgstr "Microsoft Entra Admin Center öffnen → App-Registrierungen → Neue Registrierung."
|
||||
|
||||
#: includes/class-m365-login-admin.php:441
|
||||
#: includes/class-m365-login-admin.php:559
|
||||
msgid "Choose \"Accounts in this organizational directory only\", set the platform to Web and paste the redirect URI above."
|
||||
msgstr "„Nur Konten in diesem Organisationsverzeichnis“ wählen, Plattform „Web“ auswählen und die Umleitungs-URI von oben einfügen."
|
||||
|
||||
#: includes/class-m365-login-admin.php:442
|
||||
#: includes/class-m365-login-admin.php:560
|
||||
msgid "Copy the Application (client) ID and Directory (tenant) ID from the overview page."
|
||||
msgstr "Anwendungs-ID (Client) und Verzeichnis-ID (Mandant) von der Übersichtsseite kopieren."
|
||||
|
||||
#: includes/class-m365-login-admin.php:443
|
||||
#: includes/class-m365-login-admin.php:561
|
||||
msgid "Under Certificates & secrets create a client secret and copy its value (not the ID)."
|
||||
msgstr "Unter „Zertifikate & Geheimnisse“ einen geheimen Clientschlüssel erstellen und dessen Wert (nicht die ID) kopieren."
|
||||
|
||||
#: includes/class-m365-login-admin.php:444
|
||||
#: includes/class-m365-login-admin.php:562
|
||||
msgid "Under Token configuration add the optional claim \"email\" for ID tokens (recommended), then save this page."
|
||||
msgstr "Unter „Tokenkonfiguration“ den optionalen Anspruch „email“ für ID-Tokens hinzufügen (empfohlen), dann diese Seite speichern."
|
||||
|
||||
#: includes/class-m365-login-admin.php:446
|
||||
#: includes/class-m365-login-admin.php:564
|
||||
msgid "Required API permission: openid, profile, email (delegated) – granted by default."
|
||||
msgstr "Benötigte API-Berechtigungen: openid, profile, email (delegiert) – standardmäßig vorhanden."
|
||||
|
||||
#: includes/class-m365-login-admin.php:450
|
||||
#: includes/class-m365-login-admin.php:565
|
||||
msgid "Optional, for group restrictions: application permissions GroupMember.Read.All and User.Read.All (Microsoft Graph) with admin consent."
|
||||
msgstr "Optional für Gruppen-Beschränkungen: Anwendungsberechtigungen GroupMember.Read.All und User.Read.All (Microsoft Graph) mit Administratorzustimmung."
|
||||
|
||||
#: includes/class-m365-login-admin.php:569
|
||||
msgid "Shortcode"
|
||||
msgstr "Shortcode"
|
||||
|
||||
#: includes/class-m365-login-admin.php:451
|
||||
#: includes/class-m365-login-admin.php:570
|
||||
msgid "Place the button on a custom login page:"
|
||||
msgstr "Button auf einer eigenen Login-Seite platzieren:"
|
||||
|
||||
#: includes/class-m365-login-auth.php:593
|
||||
#: includes/class-m365-login-auth.php:136
|
||||
msgid "Password sign-in is disabled on this site. Please use the Microsoft button."
|
||||
msgstr "Die Anmeldung mit Passwort ist auf dieser Website deaktiviert. Bitte den Microsoft-Button verwenden."
|
||||
|
||||
#: includes/class-m365-login-auth.php:751
|
||||
msgid "Password sign-in is temporarily enabled for this browser (30 minutes)."
|
||||
msgstr "Die Passwort-Anmeldung ist für diesen Browser vorübergehend aktiviert (30 Minuten)."
|
||||
|
||||
#: includes/class-m365-login-auth.php:758 includes/class-m365-login-graph.php:63
|
||||
msgid "Microsoft login is not configured yet."
|
||||
msgstr "Die Microsoft-Anmeldung ist noch nicht eingerichtet."
|
||||
|
||||
#: includes/class-m365-login-auth.php:594
|
||||
#: includes/class-m365-login-auth.php:759
|
||||
msgid "The login request expired or was invalid. Please try again."
|
||||
msgstr "Die Anmeldeanfrage ist abgelaufen oder ungültig. Bitte erneut versuchen."
|
||||
|
||||
#: includes/class-m365-login-auth.php:595
|
||||
#: includes/class-m365-login-auth.php:760
|
||||
msgid "Microsoft sign-in was cancelled."
|
||||
msgstr "Die Microsoft-Anmeldung wurde abgebrochen."
|
||||
|
||||
#: includes/class-m365-login-auth.php:596
|
||||
#: includes/class-m365-login-auth.php:761
|
||||
msgid "Microsoft returned an error. Please try again."
|
||||
msgstr "Microsoft hat einen Fehler gemeldet. Bitte erneut versuchen."
|
||||
|
||||
#: includes/class-m365-login-auth.php:597
|
||||
#: includes/class-m365-login-auth.php:762
|
||||
msgid "Could not complete the sign-in with Microsoft. Please try again or contact an administrator."
|
||||
msgstr "Die Anmeldung über Microsoft konnte nicht abgeschlossen werden. Bitte erneut versuchen oder einen Administrator kontaktieren."
|
||||
|
||||
#: includes/class-m365-login-auth.php:598
|
||||
#: includes/class-m365-login-auth.php:763
|
||||
msgid "The Microsoft sign-in could not be verified."
|
||||
msgstr "Die Microsoft-Anmeldung konnte nicht verifiziert werden."
|
||||
|
||||
#: includes/class-m365-login-auth.php:599
|
||||
#: includes/class-m365-login-auth.php:764
|
||||
msgid "Your Microsoft account did not provide an e-mail address."
|
||||
msgstr "Das Microsoft-Konto hat keine E-Mail-Adresse übermittelt."
|
||||
|
||||
#: includes/class-m365-login-auth.php:600
|
||||
#: includes/class-m365-login-auth.php:765
|
||||
msgid "Your e-mail domain is not allowed to sign in here."
|
||||
msgstr "Diese E-Mail-Domain ist hier nicht zur Anmeldung zugelassen."
|
||||
|
||||
#: includes/class-m365-login-auth.php:601
|
||||
#: includes/class-m365-login-auth.php:766
|
||||
msgid "No WordPress account exists for your Microsoft e-mail address."
|
||||
msgstr "Für die E-Mail-Adresse des Microsoft-Kontos existiert kein WordPress-Konto."
|
||||
|
||||
#: includes/class-m365-login-auth.php:602
|
||||
#: includes/class-m365-login-auth.php:767
|
||||
msgid "This WordPress account is linked to a different Microsoft account. Please contact an administrator."
|
||||
msgstr "Dieses WordPress-Konto ist mit einem anderen Microsoft-Konto verknüpft. Bitte einen Administrator kontaktieren."
|
||||
|
||||
#: includes/class-m365-login-auth.php:603
|
||||
#: includes/class-m365-login-auth.php:768
|
||||
msgid "You are not allowed to sign in with this account."
|
||||
msgstr "Die Anmeldung mit diesem Konto ist nicht erlaubt."
|
||||
|
||||
#: includes/class-m365-login-settings.php:40
|
||||
#: includes/class-m365-login-auth.php:769
|
||||
msgid "Your Microsoft account is not a member of a group that is allowed to sign in here."
|
||||
msgstr "Das Microsoft-Konto ist in keiner Gruppe, die sich hier anmelden darf."
|
||||
|
||||
#: includes/class-m365-login-auth.php:770
|
||||
msgid "Your group membership could not be verified. Please contact an administrator."
|
||||
msgstr "Die Gruppenmitgliedschaft konnte nicht geprüft werden. Bitte einen Administrator kontaktieren."
|
||||
|
||||
#: includes/class-m365-login-auth.php:771
|
||||
msgid "The fallback key is not valid."
|
||||
msgstr "Der Fallback-Schlüssel ist ungültig."
|
||||
|
||||
#: includes/class-m365-login-auth.php:772
|
||||
msgid "Too many attempts. Please wait 15 minutes."
|
||||
msgstr "Zu viele Versuche. Bitte 15 Minuten warten."
|
||||
|
||||
#: includes/class-m365-login-graph.php:193
|
||||
msgid "Group"
|
||||
msgstr "Gruppe"
|
||||
|
||||
#: includes/class-m365-login-graph.php:195
|
||||
msgid "Security group"
|
||||
msgstr "Sicherheitsgruppe"
|
||||
|
||||
#: includes/class-m365-login-graph.php:197
|
||||
msgid "Microsoft 365 group"
|
||||
msgstr "Microsoft 365-Gruppe"
|
||||
|
||||
#: includes/class-m365-login-settings.php:44
|
||||
msgid "Sign in with Microsoft"
|
||||
msgstr "Login mit Microsoft"
|
||||
|
||||
#: includes/class-m365-login-settings.php:49
|
||||
#: includes/class-m365-login-settings.php:53
|
||||
msgid "or"
|
||||
msgstr "oder"
|
||||
|
||||
#: includes/class-m365-login-settings.php:176
|
||||
#: includes/class-m365-login-settings.php:245
|
||||
msgid "The tenant ID must be a GUID (e.g. 1a2b3c4d-…) or one of \"organizations\", \"common\", \"consumers\"."
|
||||
msgstr "Die Tenant-ID muss eine GUID (z. B. 1a2b3c4d-…) oder einer der Werte „organizations“, „common“, „consumers“ sein."
|
||||
|
||||
#: includes/class-m365-login-settings.php:184
|
||||
#: includes/class-m365-login-settings.php:253
|
||||
msgid "The application (client) ID must be a GUID."
|
||||
msgstr "Die Anwendungs-ID (Client) muss eine GUID sein."
|
||||
|
||||
#: includes/class-m365-login-settings.php:196
|
||||
#: includes/class-m365-login-settings.php:265
|
||||
msgid "The client secret contains invalid characters."
|
||||
msgstr "Das Client Secret enthält ungültige Zeichen."
|
||||
|
||||
#: includes/class-m365-login-settings.php:200
|
||||
#: includes/class-m365-login-settings.php:269
|
||||
msgid "The client secret could not be encrypted. Is the OpenSSL extension available?"
|
||||
msgstr "Das Client Secret konnte nicht verschlüsselt werden. Ist die OpenSSL-Erweiterung verfügbar?"
|
||||
|
||||
#: includes/class-m365-login.php:94
|
||||
#: includes/class-m365-login.php:102
|
||||
msgid "Settings"
|
||||
msgstr "Einstellungen"
|
||||
|
||||
#: includes/class-m365-login.php:105
|
||||
#: includes/class-m365-login.php:113
|
||||
msgid "M365 Login requires PHP 7.4 or newer."
|
||||
msgstr "M365 Login benötigt PHP 7.4 oder neuer."
|
||||
|
||||
#: includes/class-m365-login.php:106 includes/class-m365-login.php:115
|
||||
#: includes/class-m365-login.php:114 includes/class-m365-login.php:123
|
||||
msgid "Plugin activation failed"
|
||||
msgstr "Plugin-Aktivierung fehlgeschlagen"
|
||||
|
||||
#: includes/class-m365-login.php:114
|
||||
#: includes/class-m365-login.php:122
|
||||
msgid "M365 Login requires the PHP OpenSSL extension (needed to verify Microsoft token signatures and to encrypt the client secret)."
|
||||
msgstr "M365 Login benötigt die PHP-Erweiterung OpenSSL (zur Prüfung der Microsoft-Token-Signaturen und zur Verschlüsselung des Client Secrets)."
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -16,445 +16,587 @@ msgstr ""
|
|||
"X-Generator: bin/make-pot.py\n"
|
||||
"X-Domain: m365-login\n"
|
||||
|
||||
#: includes/class-m365-login-admin.php:63 includes/class-m365-login-admin.php:64 includes/class-m365-login-admin.php:203
|
||||
#: includes/class-m365-login-admin.php:75 includes/class-m365-login-admin.php:76 includes/class-m365-login-admin.php:248
|
||||
msgid "M365 Login"
|
||||
msgstr "M365 Login"
|
||||
|
||||
#: includes/class-m365-login-admin.php:102
|
||||
#: includes/class-m365-login-admin.php:114
|
||||
msgid "M365 Login is active but not connected to Microsoft Entra ID yet."
|
||||
msgstr "M365 Login ist aktiv, aber noch nicht mit Microsoft Entra ID verbunden."
|
||||
|
||||
#: includes/class-m365-login-admin.php:104
|
||||
#: includes/class-m365-login-admin.php:116
|
||||
msgid "Open the settings"
|
||||
msgstr "Einstellungen öffnen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:132
|
||||
#: includes/class-m365-login-admin.php:145
|
||||
msgid "Choose button icon"
|
||||
msgstr "Button-Icon auswählen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:133
|
||||
#: includes/class-m365-login-admin.php:146
|
||||
msgid "Use this icon"
|
||||
msgstr "Dieses Icon verwenden"
|
||||
|
||||
#: includes/class-m365-login-admin.php:134
|
||||
#: includes/class-m365-login-admin.php:147
|
||||
msgid "Copied!"
|
||||
msgstr "Kopiert!"
|
||||
|
||||
#: includes/class-m365-login-admin.php:135 includes/class-m365-login-admin.php:427
|
||||
#: includes/class-m365-login-admin.php:148 includes/class-m365-login-admin.php:502 includes/class-m365-login-admin.php:545
|
||||
msgid "Copy"
|
||||
msgstr "Kopieren"
|
||||
|
||||
#: includes/class-m365-login-admin.php:136
|
||||
#: includes/class-m365-login-admin.php:149
|
||||
msgid "Testing…"
|
||||
msgstr "Wird geprüft …"
|
||||
|
||||
#: includes/class-m365-login-admin.php:137
|
||||
#: includes/class-m365-login-admin.php:150
|
||||
msgid "The tenant could not be reached. Check the tenant ID and the server’s outgoing connections."
|
||||
msgstr "Der Tenant ist nicht erreichbar. Bitte Tenant-ID und ausgehende Verbindungen des Servers prüfen."
|
||||
|
||||
#: includes/class-m365-login-admin.php:149
|
||||
#: includes/class-m365-login-admin.php:151
|
||||
msgid "No groups found."
|
||||
msgstr "Keine Gruppen gefunden."
|
||||
|
||||
#: includes/class-m365-login-admin.php:152
|
||||
msgid "Searching…"
|
||||
msgstr "Suche läuft …"
|
||||
|
||||
#: includes/class-m365-login-admin.php:153
|
||||
msgid "Add"
|
||||
msgstr "Hinzufügen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:154 includes/class-m365-login-admin.php:476
|
||||
msgid "Remove"
|
||||
msgstr "Entfernen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:155 includes/class-m365-login-admin.php:210 includes/class-m365-login-admin.php:461
|
||||
msgid "Save the connection settings first, then search for groups."
|
||||
msgstr "Zuerst die Verbindungseinstellungen speichern, dann Gruppen suchen."
|
||||
|
||||
#: includes/class-m365-login-admin.php:156
|
||||
msgid "Generate a new fallback key on save? The old link stops working."
|
||||
msgstr "Beim Speichern einen neuen Fallback-Schlüssel erzeugen? Der alte Link funktioniert dann nicht mehr."
|
||||
|
||||
#: includes/class-m365-login-admin.php:168 includes/class-m365-login-admin.php:207
|
||||
msgid "You are not allowed to do this."
|
||||
msgstr "Dafür fehlt die Berechtigung."
|
||||
|
||||
#: includes/class-m365-login-admin.php:154
|
||||
#: includes/class-m365-login-admin.php:173
|
||||
msgid "Please enter a valid tenant ID first."
|
||||
msgstr "Bitte zuerst eine gültige Tenant-ID eingeben."
|
||||
|
||||
#. translators: %d: HTTP status code
|
||||
#: includes/class-m365-login-admin.php:168
|
||||
#: includes/class-m365-login-admin.php:187
|
||||
msgid "Microsoft answered with HTTP %d. Is the tenant ID correct?"
|
||||
msgstr "Microsoft hat mit HTTP %d geantwortet. Ist die Tenant-ID korrekt?"
|
||||
|
||||
#. translators: %d: HTTP status code
|
||||
#: includes/class-m365-login-admin.php:177
|
||||
#: includes/class-m365-login-admin.php:196
|
||||
msgid "Tenant reachable. The OpenID configuration was loaded successfully."
|
||||
msgstr "Tenant erreichbar. Die OpenID-Konfiguration wurde erfolgreich geladen."
|
||||
|
||||
#: includes/class-m365-login-admin.php:187
|
||||
#: includes/class-m365-login-admin.php:219
|
||||
msgid "Microsoft Graph refused the request. Grant the application permission \"GroupMember.Read.All\" (or \"Directory.Read.All\") with admin consent in Entra ID."
|
||||
msgstr "Microsoft Graph hat die Anfrage abgelehnt. In Entra ID die Anwendungsberechtigung „GroupMember.Read.All“ (oder „Directory.Read.All“) mit Administratorzustimmung erteilen."
|
||||
|
||||
#: includes/class-m365-login-admin.php:232
|
||||
msgid "You are not allowed to access this page."
|
||||
msgstr "Für diese Seite fehlt die Berechtigung."
|
||||
|
||||
#: includes/class-m365-login-admin.php:204
|
||||
#: includes/class-m365-login-admin.php:249
|
||||
msgid "Let existing users sign in with their Microsoft 365 / Entra ID account."
|
||||
msgstr "Bestehende Benutzer melden sich mit ihrem Microsoft 365 / Entra ID-Konto an."
|
||||
|
||||
#: includes/class-m365-login-admin.php:209
|
||||
#: includes/class-m365-login-admin.php:254
|
||||
msgid "Connected"
|
||||
msgstr "Verbunden"
|
||||
|
||||
#: includes/class-m365-login-admin.php:209
|
||||
#: includes/class-m365-login-admin.php:254
|
||||
msgid "Setup incomplete"
|
||||
msgstr "Einrichtung unvollständig"
|
||||
|
||||
#: includes/class-m365-login-admin.php:217
|
||||
#: includes/class-m365-login-admin.php:262
|
||||
msgid "Connection"
|
||||
msgstr "Verbindung"
|
||||
|
||||
#: includes/class-m365-login-admin.php:218
|
||||
#: includes/class-m365-login-admin.php:263
|
||||
msgid "Button"
|
||||
msgstr "Button"
|
||||
|
||||
#: includes/class-m365-login-admin.php:219
|
||||
#: includes/class-m365-login-admin.php:264
|
||||
msgid "Security"
|
||||
msgstr "Sicherheit"
|
||||
|
||||
#: includes/class-m365-login-admin.php:228
|
||||
#: includes/class-m365-login-admin.php:273
|
||||
msgid "Microsoft Entra ID app registration"
|
||||
msgstr "App-Registrierung in Microsoft Entra ID"
|
||||
|
||||
#: includes/class-m365-login-admin.php:229
|
||||
#: includes/class-m365-login-admin.php:274
|
||||
msgid "Enter the values from your app registration in the Microsoft Entra admin center."
|
||||
msgstr "Tragen Sie hier die Werte aus Ihrer App-Registrierung im Microsoft Entra Admin Center ein."
|
||||
|
||||
#: includes/class-m365-login-admin.php:232
|
||||
#: includes/class-m365-login-admin.php:277
|
||||
msgid "Directory (tenant) ID"
|
||||
msgstr "Verzeichnis-ID (Mandant/Tenant)"
|
||||
|
||||
#: includes/class-m365-login-admin.php:235
|
||||
#: includes/class-m365-login-admin.php:280
|
||||
msgid "Test tenant"
|
||||
msgstr "Tenant testen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:237
|
||||
#: includes/class-m365-login-admin.php:282
|
||||
msgid "Recommended: the GUID of your tenant. Only sign-ins from this tenant are accepted. \"organizations\" allows any work or school account."
|
||||
msgstr "Empfohlen: die GUID Ihres Tenants. Dann werden nur Anmeldungen aus diesem Tenant akzeptiert. „organizations“ erlaubt beliebige Geschäfts-, Schul- oder Unikonten."
|
||||
|
||||
#: includes/class-m365-login-admin.php:242
|
||||
#: includes/class-m365-login-admin.php:287
|
||||
msgid "Application (client) ID"
|
||||
msgstr "Anwendungs-ID (Client)"
|
||||
|
||||
#: includes/class-m365-login-admin.php:247
|
||||
#: includes/class-m365-login-admin.php:292
|
||||
msgid "Client secret"
|
||||
msgstr "Geheimer Clientschlüssel (Client Secret)"
|
||||
|
||||
#: includes/class-m365-login-admin.php:249
|
||||
#: includes/class-m365-login-admin.php:294
|
||||
msgid "•••••••••••• (stored, leave empty to keep)"
|
||||
msgstr "•••••••••••• (gespeichert – leer lassen, um zu behalten)"
|
||||
|
||||
#: includes/class-m365-login-admin.php:249
|
||||
#: includes/class-m365-login-admin.php:294
|
||||
msgid "Paste the secret value"
|
||||
msgstr "Wert des Secrets einfügen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:250
|
||||
#: includes/class-m365-login-admin.php:295
|
||||
msgid "Show secret"
|
||||
msgstr "Secret anzeigen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:255
|
||||
#: includes/class-m365-login-admin.php:300
|
||||
msgid "Remove the stored secret"
|
||||
msgstr "Gespeichertes Secret entfernen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:258
|
||||
#: includes/class-m365-login-admin.php:303
|
||||
msgid "Stored encrypted (AES-256-GCM, key derived from your WordPress salts) and never displayed again. Client secrets expire – note the expiry date in Entra ID."
|
||||
msgstr "Wird verschlüsselt gespeichert (AES-256-GCM, Schlüssel aus den WordPress-Salts abgeleitet) und nie wieder angezeigt. Client Secrets laufen ab – Ablaufdatum in Entra ID notieren."
|
||||
|
||||
#: includes/class-m365-login-admin.php:262
|
||||
#: includes/class-m365-login-admin.php:307
|
||||
msgid "Account prompt"
|
||||
msgstr "Kontoauswahl"
|
||||
|
||||
#: includes/class-m365-login-admin.php:264
|
||||
#: includes/class-m365-login-admin.php:309
|
||||
msgid "Always let the user pick an account (recommended)"
|
||||
msgstr "Benutzer wählt immer ein Konto aus (empfohlen)"
|
||||
|
||||
#: includes/class-m365-login-admin.php:265
|
||||
#: includes/class-m365-login-admin.php:310
|
||||
msgid "Use the current Microsoft session if available"
|
||||
msgstr "Vorhandene Microsoft-Sitzung verwenden, falls vorhanden"
|
||||
|
||||
#: includes/class-m365-login-admin.php:266
|
||||
#: includes/class-m365-login-admin.php:311
|
||||
msgid "Always require re-entering credentials"
|
||||
msgstr "Immer erneute Eingabe der Anmeldedaten verlangen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:275
|
||||
#: includes/class-m365-login-admin.php:320
|
||||
msgid "Appearance"
|
||||
msgstr "Darstellung"
|
||||
|
||||
#: includes/class-m365-login-admin.php:278
|
||||
#: includes/class-m365-login-admin.php:323
|
||||
msgid "Live preview"
|
||||
msgstr "Live-Vorschau"
|
||||
|
||||
#: includes/class-m365-login-admin.php:292
|
||||
#: includes/class-m365-login-admin.php:337
|
||||
msgid "Button text"
|
||||
msgstr "Button-Text"
|
||||
|
||||
#: includes/class-m365-login-admin.php:296
|
||||
#: includes/class-m365-login-admin.php:341
|
||||
msgid "Divider text"
|
||||
msgstr "Trennlinien-Text"
|
||||
|
||||
#: includes/class-m365-login-admin.php:298
|
||||
#: includes/class-m365-login-admin.php:343
|
||||
msgid "Leave empty to hide the divider line."
|
||||
msgstr "Leer lassen, um die Trennlinie auszublenden."
|
||||
|
||||
#: includes/class-m365-login-admin.php:303
|
||||
#: includes/class-m365-login-admin.php:348
|
||||
msgid "Icon"
|
||||
msgstr "Icon"
|
||||
|
||||
#: includes/class-m365-login-admin.php:306
|
||||
#: includes/class-m365-login-admin.php:351
|
||||
msgid "Show an icon on the button"
|
||||
msgstr "Icon auf dem Button anzeigen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:317
|
||||
#: includes/class-m365-login-admin.php:362
|
||||
msgid "Default: Microsoft logo"
|
||||
msgstr "Standard: Microsoft-Logo"
|
||||
|
||||
#: includes/class-m365-login-admin.php:319
|
||||
#: includes/class-m365-login-admin.php:364
|
||||
msgid "Choose from media library"
|
||||
msgstr "Aus Mediathek wählen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:320
|
||||
#: includes/class-m365-login-admin.php:365
|
||||
msgid "Use Microsoft logo"
|
||||
msgstr "Microsoft-Logo verwenden"
|
||||
|
||||
#: includes/class-m365-login-admin.php:322
|
||||
#: includes/class-m365-login-admin.php:367
|
||||
msgid "PNG, SVG, JPG or WebP. Square images (e.g. 64×64 px) work best."
|
||||
msgstr "PNG, SVG, JPG oder WebP. Quadratische Bilder (z. B. 64×64 px) eignen sich am besten."
|
||||
|
||||
#: includes/class-m365-login-admin.php:330
|
||||
#: includes/class-m365-login-admin.php:375
|
||||
msgid "Background"
|
||||
msgstr "Hintergrund"
|
||||
|
||||
#: includes/class-m365-login-admin.php:331
|
||||
#: includes/class-m365-login-admin.php:376
|
||||
msgid "Background (hover)"
|
||||
msgstr "Hintergrund (Hover)"
|
||||
|
||||
#: includes/class-m365-login-admin.php:332
|
||||
#: includes/class-m365-login-admin.php:377
|
||||
msgid "Text colour"
|
||||
msgstr "Textfarbe"
|
||||
|
||||
#: includes/class-m365-login-admin.php:333
|
||||
#: includes/class-m365-login-admin.php:378
|
||||
msgid "Border"
|
||||
msgstr "Rahmen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:346
|
||||
#: includes/class-m365-login-admin.php:391
|
||||
msgid "Corner radius"
|
||||
msgstr "Eckenradius"
|
||||
|
||||
#: includes/class-m365-login-admin.php:350
|
||||
#: includes/class-m365-login-admin.php:395
|
||||
msgid "Position on the login page"
|
||||
msgstr "Position auf der Login-Seite"
|
||||
|
||||
#: includes/class-m365-login-admin.php:352
|
||||
#: includes/class-m365-login-admin.php:397
|
||||
msgid "Below the login form"
|
||||
msgstr "Unter dem Login-Formular"
|
||||
|
||||
#: includes/class-m365-login-admin.php:353
|
||||
#: includes/class-m365-login-admin.php:398
|
||||
msgid "Above the login form"
|
||||
msgstr "Über dem Login-Formular"
|
||||
|
||||
#: includes/class-m365-login-admin.php:359
|
||||
#: includes/class-m365-login-admin.php:404
|
||||
msgid "Quick presets"
|
||||
msgstr "Schnellauswahl"
|
||||
|
||||
#: includes/class-m365-login-admin.php:360
|
||||
#: includes/class-m365-login-admin.php:405
|
||||
msgid "Microsoft dark"
|
||||
msgstr "Microsoft dunkel"
|
||||
|
||||
#: includes/class-m365-login-admin.php:361
|
||||
#: includes/class-m365-login-admin.php:406
|
||||
msgid "Microsoft light"
|
||||
msgstr "Microsoft hell"
|
||||
|
||||
#: includes/class-m365-login-admin.php:362
|
||||
#: includes/class-m365-login-admin.php:407
|
||||
msgid "Azure blue"
|
||||
msgstr "Azure-Blau"
|
||||
|
||||
#: includes/class-m365-login-admin.php:363
|
||||
#: includes/class-m365-login-admin.php:408
|
||||
msgid "WordPress blue"
|
||||
msgstr "WordPress-Blau"
|
||||
|
||||
#: includes/class-m365-login-admin.php:371
|
||||
#: includes/class-m365-login-admin.php:416
|
||||
msgid "User matching & hardening"
|
||||
msgstr "Benutzerzuordnung & Härtung"
|
||||
|
||||
#: includes/class-m365-login-admin.php:372
|
||||
#: includes/class-m365-login-admin.php:417
|
||||
msgid "Users are never created automatically. A Microsoft sign-in only succeeds when a WordPress user with the same e-mail address already exists."
|
||||
msgstr "Benutzer werden nie automatisch angelegt. Eine Microsoft-Anmeldung gelingt nur, wenn bereits ein WordPress-Benutzer mit derselben E-Mail-Adresse existiert."
|
||||
|
||||
#: includes/class-m365-login-admin.php:377
|
||||
#: includes/class-m365-login-admin.php:422
|
||||
msgid "Bind WordPress accounts to the Microsoft object ID"
|
||||
msgstr "WordPress-Konten an die Microsoft-Objekt-ID binden"
|
||||
|
||||
#: includes/class-m365-login-admin.php:378
|
||||
#: includes/class-m365-login-admin.php:423
|
||||
msgid "On first sign-in the immutable Microsoft object ID is stored with the user. Later sign-ins with the same e-mail but a different Microsoft identity are rejected. Strongly recommended."
|
||||
msgstr "Bei der ersten Anmeldung wird die unveränderliche Microsoft-Objekt-ID am Benutzer gespeichert. Spätere Anmeldungen mit gleicher E-Mail, aber anderer Microsoft-Identität werden abgelehnt. Dringend empfohlen."
|
||||
|
||||
#: includes/class-m365-login-admin.php:385
|
||||
#: includes/class-m365-login-admin.php:430
|
||||
msgid "Fall back to the user principal name (UPN)"
|
||||
msgstr "Auf den User Principal Name (UPN) zurückgreifen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:386
|
||||
#: includes/class-m365-login-admin.php:431
|
||||
msgid "If the token contains no \"email\" claim, use the UPN (e.g. jane@contoso.com) when it is a valid e-mail address. Usually required for work accounts."
|
||||
msgstr "Enthält das Token keinen „email“-Claim, wird der UPN (z. B. jane@contoso.com) verwendet, sofern er eine gültige E-Mail-Adresse ist. Für Geschäftskonten meist erforderlich."
|
||||
|
||||
#: includes/class-m365-login-admin.php:393
|
||||
#: includes/class-m365-login-admin.php:438
|
||||
msgid "Keep users signed in (\"Remember me\")"
|
||||
msgstr "Benutzer angemeldet lassen („Angemeldet bleiben“)"
|
||||
|
||||
#: includes/class-m365-login-admin.php:394
|
||||
#: includes/class-m365-login-admin.php:439
|
||||
msgid "Issues a 14-day WordPress session instead of a browser session."
|
||||
msgstr "Erstellt eine 14-tägige WordPress-Sitzung statt einer Browser-Sitzung."
|
||||
|
||||
#: includes/class-m365-login-admin.php:399
|
||||
#: includes/class-m365-login-admin.php:444
|
||||
msgid "Allowed e-mail domains (optional)"
|
||||
msgstr "Erlaubte E-Mail-Domains (optional)"
|
||||
|
||||
#: includes/class-m365-login-admin.php:401
|
||||
#: includes/class-m365-login-admin.php:446
|
||||
msgid "One or more domains separated by commas or new lines. Leave empty to allow any domain of your tenant."
|
||||
msgstr "Eine oder mehrere Domains, getrennt durch Kommas oder Zeilenumbrüche. Leer lassen, um alle Domains des Tenants zuzulassen."
|
||||
|
||||
#: includes/class-m365-login-admin.php:406
|
||||
#: includes/class-m365-login-admin.php:451
|
||||
msgid "Allowed Entra groups (optional)"
|
||||
msgstr "Erlaubte Entra-Gruppen (optional)"
|
||||
|
||||
#: includes/class-m365-login-admin.php:452
|
||||
msgid "Only members of at least one of these groups may sign in. Leave empty to allow every matched user. Nested memberships count."
|
||||
msgstr "Nur Mitglieder mindestens einer dieser Gruppen dürfen sich anmelden. Leer lassen, um alle zugeordneten Benutzer zuzulassen. Verschachtelte Mitgliedschaften zählen."
|
||||
|
||||
#: includes/class-m365-login-admin.php:455
|
||||
msgid "Search groups"
|
||||
msgstr "Gruppen suchen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:457
|
||||
msgid "Type a group name or paste an object ID…"
|
||||
msgstr "Gruppenname eingeben oder Objekt-ID einfügen …"
|
||||
|
||||
#: includes/class-m365-login-admin.php:458
|
||||
msgid "Search"
|
||||
msgstr "Suchen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:463
|
||||
msgid "Needs the application permission \"GroupMember.Read.All\" with admin consent. Without it you can still paste group object IDs."
|
||||
msgstr "Benötigt die Anwendungsberechtigung „GroupMember.Read.All“ mit Administratorzustimmung. Ohne sie können Gruppen-Objekt-IDs trotzdem eingefügt werden."
|
||||
|
||||
#: includes/class-m365-login-admin.php:469
|
||||
msgid "Selected groups"
|
||||
msgstr "Ausgewählte Gruppen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:470
|
||||
msgid "No groups selected – every matched user may sign in."
|
||||
msgstr "Keine Gruppen ausgewählt – jeder zugeordnete Benutzer darf sich anmelden."
|
||||
|
||||
#: includes/class-m365-login-admin.php:480
|
||||
msgid "Membership is read from the \"groups\" claim of the ID token when present; otherwise the plugin asks Microsoft Graph (application permission \"User.Read.All\" or \"Directory.Read.All\"). If neither works, the sign-in is refused."
|
||||
msgstr "Die Mitgliedschaft wird aus dem „groups“-Claim des ID-Tokens gelesen, falls vorhanden; andernfalls fragt das Plugin Microsoft Graph (Anwendungsberechtigung „User.Read.All“ oder „Directory.Read.All“). Funktioniert beides nicht, wird die Anmeldung abgelehnt."
|
||||
|
||||
#: includes/class-m365-login-admin.php:485
|
||||
msgid "Button-only mode"
|
||||
msgstr "Nur-Button-Modus"
|
||||
|
||||
#: includes/class-m365-login-admin.php:486
|
||||
msgid "Hide the username/password form and the \"Lost your password?\" link, and refuse password sign-ins on the login page. Application passwords, REST and XML-RPC are not affected."
|
||||
msgstr "Blendet das Benutzername/Passwort-Formular und den Link „Passwort vergessen?“ aus und lehnt Passwort-Anmeldungen auf der Login-Seite ab. Anwendungspasswörter, REST und XML-RPC sind nicht betroffen."
|
||||
|
||||
#: includes/class-m365-login-admin.php:491
|
||||
msgid "Show only the Microsoft button on the login page"
|
||||
msgstr "Auf der Login-Seite nur den Microsoft-Button anzeigen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:492
|
||||
msgid "Becomes active once the connection is configured. Make sure your own account can sign in via Microsoft before enabling this."
|
||||
msgstr "Wird aktiv, sobald die Verbindung eingerichtet ist. Vor dem Aktivieren sicherstellen, dass das eigene Konto sich per Microsoft anmelden kann."
|
||||
|
||||
#: includes/class-m365-login-admin.php:497
|
||||
msgid "Fallback link (keep it secret)"
|
||||
msgstr "Fallback-Link (geheim halten)"
|
||||
|
||||
#: includes/class-m365-login-admin.php:498
|
||||
msgid "Opening this link shows the password form again in that browser for 30 minutes. Bookmark it somewhere safe – it is your way back in if Microsoft sign-in ever breaks."
|
||||
msgstr "Wer diesen Link öffnet, sieht in diesem Browser 30 Minuten lang wieder das Passwort-Formular. Sicher aufbewahren – er ist der Weg zurück, falls die Microsoft-Anmeldung einmal nicht funktioniert."
|
||||
|
||||
#: includes/class-m365-login-admin.php:506
|
||||
msgid "Generate a new key when saving"
|
||||
msgstr "Beim Speichern einen neuen Schlüssel erzeugen"
|
||||
|
||||
#: includes/class-m365-login-admin.php:509
|
||||
msgid "A key is generated automatically the first time you save these settings."
|
||||
msgstr "Beim ersten Speichern dieser Einstellungen wird automatisch ein Schlüssel erzeugt."
|
||||
|
||||
#. translators: %s: PHP constant
|
||||
#: includes/class-m365-login-admin.php:515
|
||||
msgid "Emergency switch: add %s to wp-config.php to disable button-only mode entirely."
|
||||
msgstr "Notschalter: %s in die wp-config.php eintragen, um den Nur-Button-Modus vollständig abzuschalten."
|
||||
|
||||
#. translators: %s: PHP constant
|
||||
#: includes/class-m365-login-admin.php:524
|
||||
msgid "What the plugin does to keep sign-ins safe"
|
||||
msgstr "So schützt das Plugin die Anmeldung"
|
||||
|
||||
#: includes/class-m365-login-admin.php:408
|
||||
#: includes/class-m365-login-admin.php:526
|
||||
msgid "OpenID Connect authorization code flow with PKCE (S256) – no tokens ever pass through the browser."
|
||||
msgstr "OpenID Connect Authorization Code Flow mit PKCE (S256) – Tokens laufen nie durch den Browser."
|
||||
|
||||
#: includes/class-m365-login-admin.php:409
|
||||
#: includes/class-m365-login-admin.php:527
|
||||
msgid "Single-use state and nonce values bound to the browser via an HttpOnly cookie (CSRF and replay protection)."
|
||||
msgstr "Einmalige State- und Nonce-Werte, per HttpOnly-Cookie an den Browser gebunden (CSRF- und Replay-Schutz)."
|
||||
|
||||
#: includes/class-m365-login-admin.php:410
|
||||
#: includes/class-m365-login-admin.php:528
|
||||
msgid "ID token signature verified against Microsoft’s published signing keys; issuer, audience, tenant, expiry and nonce are checked."
|
||||
msgstr "Signatur des ID-Tokens wird gegen Microsofts veröffentlichte Signaturschlüssel geprüft; Issuer, Audience, Tenant, Ablauf und Nonce werden kontrolliert."
|
||||
|
||||
#: includes/class-m365-login-admin.php:411
|
||||
#: includes/class-m365-login-admin.php:529
|
||||
msgid "Client secret encrypted at rest; no accounts are created, no passwords are changed."
|
||||
msgstr "Client Secret verschlüsselt gespeichert; es werden keine Konten angelegt und keine Passwörter geändert."
|
||||
|
||||
#: includes/class-m365-login-admin.php:417
|
||||
#: includes/class-m365-login-admin.php:535
|
||||
msgid "Save changes"
|
||||
msgstr "Änderungen speichern"
|
||||
|
||||
#: includes/class-m365-login-admin.php:423
|
||||
#: includes/class-m365-login-admin.php:541
|
||||
msgid "Redirect URI"
|
||||
msgstr "Umleitungs-URI (Redirect URI)"
|
||||
|
||||
#: includes/class-m365-login-admin.php:424
|
||||
#: includes/class-m365-login-admin.php:542
|
||||
msgid "Register this URI in your app registration under Authentication → Web → Redirect URIs:"
|
||||
msgstr "Diese URI in der App-Registrierung unter Authentifizierung → Web → Umleitungs-URIs eintragen:"
|
||||
|
||||
#: includes/class-m365-login-admin.php:430
|
||||
#: includes/class-m365-login-admin.php:548
|
||||
msgid "Plain permalinks are active, so the callback uses a query string. If you enable pretty permalinks later, the redirect URI changes and must be updated in Entra ID."
|
||||
msgstr "Einfache Permalinks sind aktiv, daher verwendet der Callback einen Query-String. Werden später sprechende Permalinks aktiviert, ändert sich die Umleitungs-URI und muss in Entra ID angepasst werden."
|
||||
|
||||
#: includes/class-m365-login-admin.php:433
|
||||
#: includes/class-m365-login-admin.php:551
|
||||
msgid "Your site does not use HTTPS. Microsoft only accepts http:// redirect URIs for localhost; production sites must use HTTPS."
|
||||
msgstr "Diese Website nutzt kein HTTPS. Microsoft akzeptiert http://-Umleitungs-URIs nur für localhost; produktive Websites benötigen HTTPS."
|
||||
|
||||
#: includes/class-m365-login-admin.php:438
|
||||
#: includes/class-m365-login-admin.php:556
|
||||
msgid "Setup in 5 steps"
|
||||
msgstr "Einrichtung in 5 Schritten"
|
||||
|
||||
#: includes/class-m365-login-admin.php:440
|
||||
#: includes/class-m365-login-admin.php:558
|
||||
msgid "Open the Microsoft Entra admin center → App registrations → New registration."
|
||||
msgstr "Microsoft Entra Admin Center öffnen → App-Registrierungen → Neue Registrierung."
|
||||
|
||||
#: includes/class-m365-login-admin.php:441
|
||||
#: includes/class-m365-login-admin.php:559
|
||||
msgid "Choose \"Accounts in this organizational directory only\", set the platform to Web and paste the redirect URI above."
|
||||
msgstr "„Nur Konten in diesem Organisationsverzeichnis“ wählen, Plattform „Web“ auswählen und die Umleitungs-URI von oben einfügen."
|
||||
|
||||
#: includes/class-m365-login-admin.php:442
|
||||
#: includes/class-m365-login-admin.php:560
|
||||
msgid "Copy the Application (client) ID and Directory (tenant) ID from the overview page."
|
||||
msgstr "Anwendungs-ID (Client) und Verzeichnis-ID (Mandant) von der Übersichtsseite kopieren."
|
||||
|
||||
#: includes/class-m365-login-admin.php:443
|
||||
#: includes/class-m365-login-admin.php:561
|
||||
msgid "Under Certificates & secrets create a client secret and copy its value (not the ID)."
|
||||
msgstr "Unter „Zertifikate & Geheimnisse“ einen geheimen Clientschlüssel erstellen und dessen Wert (nicht die ID) kopieren."
|
||||
|
||||
#: includes/class-m365-login-admin.php:444
|
||||
#: includes/class-m365-login-admin.php:562
|
||||
msgid "Under Token configuration add the optional claim \"email\" for ID tokens (recommended), then save this page."
|
||||
msgstr "Unter „Tokenkonfiguration“ den optionalen Anspruch „email“ für ID-Tokens hinzufügen (empfohlen), dann diese Seite speichern."
|
||||
|
||||
#: includes/class-m365-login-admin.php:446
|
||||
#: includes/class-m365-login-admin.php:564
|
||||
msgid "Required API permission: openid, profile, email (delegated) – granted by default."
|
||||
msgstr "Benötigte API-Berechtigungen: openid, profile, email (delegiert) – standardmäßig vorhanden."
|
||||
|
||||
#: includes/class-m365-login-admin.php:450
|
||||
#: includes/class-m365-login-admin.php:565
|
||||
msgid "Optional, for group restrictions: application permissions GroupMember.Read.All and User.Read.All (Microsoft Graph) with admin consent."
|
||||
msgstr "Optional für Gruppen-Beschränkungen: Anwendungsberechtigungen GroupMember.Read.All und User.Read.All (Microsoft Graph) mit Administratorzustimmung."
|
||||
|
||||
#: includes/class-m365-login-admin.php:569
|
||||
msgid "Shortcode"
|
||||
msgstr "Shortcode"
|
||||
|
||||
#: includes/class-m365-login-admin.php:451
|
||||
#: includes/class-m365-login-admin.php:570
|
||||
msgid "Place the button on a custom login page:"
|
||||
msgstr "Button auf einer eigenen Login-Seite platzieren:"
|
||||
|
||||
#: includes/class-m365-login-auth.php:593
|
||||
#: includes/class-m365-login-auth.php:136
|
||||
msgid "Password sign-in is disabled on this site. Please use the Microsoft button."
|
||||
msgstr "Die Anmeldung mit Passwort ist auf dieser Website deaktiviert. Bitte den Microsoft-Button verwenden."
|
||||
|
||||
#: includes/class-m365-login-auth.php:751
|
||||
msgid "Password sign-in is temporarily enabled for this browser (30 minutes)."
|
||||
msgstr "Die Passwort-Anmeldung ist für diesen Browser vorübergehend aktiviert (30 Minuten)."
|
||||
|
||||
#: includes/class-m365-login-auth.php:758 includes/class-m365-login-graph.php:63
|
||||
msgid "Microsoft login is not configured yet."
|
||||
msgstr "Die Microsoft-Anmeldung ist noch nicht eingerichtet."
|
||||
|
||||
#: includes/class-m365-login-auth.php:594
|
||||
#: includes/class-m365-login-auth.php:759
|
||||
msgid "The login request expired or was invalid. Please try again."
|
||||
msgstr "Die Anmeldeanfrage ist abgelaufen oder ungültig. Bitte erneut versuchen."
|
||||
|
||||
#: includes/class-m365-login-auth.php:595
|
||||
#: includes/class-m365-login-auth.php:760
|
||||
msgid "Microsoft sign-in was cancelled."
|
||||
msgstr "Die Microsoft-Anmeldung wurde abgebrochen."
|
||||
|
||||
#: includes/class-m365-login-auth.php:596
|
||||
#: includes/class-m365-login-auth.php:761
|
||||
msgid "Microsoft returned an error. Please try again."
|
||||
msgstr "Microsoft hat einen Fehler gemeldet. Bitte erneut versuchen."
|
||||
|
||||
#: includes/class-m365-login-auth.php:597
|
||||
#: includes/class-m365-login-auth.php:762
|
||||
msgid "Could not complete the sign-in with Microsoft. Please try again or contact an administrator."
|
||||
msgstr "Die Anmeldung über Microsoft konnte nicht abgeschlossen werden. Bitte erneut versuchen oder einen Administrator kontaktieren."
|
||||
|
||||
#: includes/class-m365-login-auth.php:598
|
||||
#: includes/class-m365-login-auth.php:763
|
||||
msgid "The Microsoft sign-in could not be verified."
|
||||
msgstr "Die Microsoft-Anmeldung konnte nicht verifiziert werden."
|
||||
|
||||
#: includes/class-m365-login-auth.php:599
|
||||
#: includes/class-m365-login-auth.php:764
|
||||
msgid "Your Microsoft account did not provide an e-mail address."
|
||||
msgstr "Das Microsoft-Konto hat keine E-Mail-Adresse übermittelt."
|
||||
|
||||
#: includes/class-m365-login-auth.php:600
|
||||
#: includes/class-m365-login-auth.php:765
|
||||
msgid "Your e-mail domain is not allowed to sign in here."
|
||||
msgstr "Diese E-Mail-Domain ist hier nicht zur Anmeldung zugelassen."
|
||||
|
||||
#: includes/class-m365-login-auth.php:601
|
||||
#: includes/class-m365-login-auth.php:766
|
||||
msgid "No WordPress account exists for your Microsoft e-mail address."
|
||||
msgstr "Für die E-Mail-Adresse des Microsoft-Kontos existiert kein WordPress-Konto."
|
||||
|
||||
#: includes/class-m365-login-auth.php:602
|
||||
#: includes/class-m365-login-auth.php:767
|
||||
msgid "This WordPress account is linked to a different Microsoft account. Please contact an administrator."
|
||||
msgstr "Dieses WordPress-Konto ist mit einem anderen Microsoft-Konto verknüpft. Bitte einen Administrator kontaktieren."
|
||||
|
||||
#: includes/class-m365-login-auth.php:603
|
||||
#: includes/class-m365-login-auth.php:768
|
||||
msgid "You are not allowed to sign in with this account."
|
||||
msgstr "Die Anmeldung mit diesem Konto ist nicht erlaubt."
|
||||
|
||||
#: includes/class-m365-login-settings.php:40
|
||||
#: includes/class-m365-login-auth.php:769
|
||||
msgid "Your Microsoft account is not a member of a group that is allowed to sign in here."
|
||||
msgstr "Das Microsoft-Konto ist in keiner Gruppe, die sich hier anmelden darf."
|
||||
|
||||
#: includes/class-m365-login-auth.php:770
|
||||
msgid "Your group membership could not be verified. Please contact an administrator."
|
||||
msgstr "Die Gruppenmitgliedschaft konnte nicht geprüft werden. Bitte einen Administrator kontaktieren."
|
||||
|
||||
#: includes/class-m365-login-auth.php:771
|
||||
msgid "The fallback key is not valid."
|
||||
msgstr "Der Fallback-Schlüssel ist ungültig."
|
||||
|
||||
#: includes/class-m365-login-auth.php:772
|
||||
msgid "Too many attempts. Please wait 15 minutes."
|
||||
msgstr "Zu viele Versuche. Bitte 15 Minuten warten."
|
||||
|
||||
#: includes/class-m365-login-graph.php:193
|
||||
msgid "Group"
|
||||
msgstr "Gruppe"
|
||||
|
||||
#: includes/class-m365-login-graph.php:195
|
||||
msgid "Security group"
|
||||
msgstr "Sicherheitsgruppe"
|
||||
|
||||
#: includes/class-m365-login-graph.php:197
|
||||
msgid "Microsoft 365 group"
|
||||
msgstr "Microsoft 365-Gruppe"
|
||||
|
||||
#: includes/class-m365-login-settings.php:44
|
||||
msgid "Sign in with Microsoft"
|
||||
msgstr "Login mit Microsoft"
|
||||
|
||||
#: includes/class-m365-login-settings.php:49
|
||||
#: includes/class-m365-login-settings.php:53
|
||||
msgid "or"
|
||||
msgstr "oder"
|
||||
|
||||
#: includes/class-m365-login-settings.php:176
|
||||
#: includes/class-m365-login-settings.php:245
|
||||
msgid "The tenant ID must be a GUID (e.g. 1a2b3c4d-…) or one of \"organizations\", \"common\", \"consumers\"."
|
||||
msgstr "Die Tenant-ID muss eine GUID (z. B. 1a2b3c4d-…) oder einer der Werte „organizations“, „common“, „consumers“ sein."
|
||||
|
||||
#: includes/class-m365-login-settings.php:184
|
||||
#: includes/class-m365-login-settings.php:253
|
||||
msgid "The application (client) ID must be a GUID."
|
||||
msgstr "Die Anwendungs-ID (Client) muss eine GUID sein."
|
||||
|
||||
#: includes/class-m365-login-settings.php:196
|
||||
#: includes/class-m365-login-settings.php:265
|
||||
msgid "The client secret contains invalid characters."
|
||||
msgstr "Das Client Secret enthält ungültige Zeichen."
|
||||
|
||||
#: includes/class-m365-login-settings.php:200
|
||||
#: includes/class-m365-login-settings.php:269
|
||||
msgid "The client secret could not be encrypted. Is the OpenSSL extension available?"
|
||||
msgstr "Das Client Secret konnte nicht verschlüsselt werden. Ist die OpenSSL-Erweiterung verfügbar?"
|
||||
|
||||
#: includes/class-m365-login.php:94
|
||||
#: includes/class-m365-login.php:102
|
||||
msgid "Settings"
|
||||
msgstr "Einstellungen"
|
||||
|
||||
#: includes/class-m365-login.php:105
|
||||
#: includes/class-m365-login.php:113
|
||||
msgid "M365 Login requires PHP 7.4 or newer."
|
||||
msgstr "M365 Login benötigt PHP 7.4 oder neuer."
|
||||
|
||||
#: includes/class-m365-login.php:106 includes/class-m365-login.php:115
|
||||
#: includes/class-m365-login.php:114 includes/class-m365-login.php:123
|
||||
msgid "Plugin activation failed"
|
||||
msgstr "Plugin-Aktivierung fehlgeschlagen"
|
||||
|
||||
#: includes/class-m365-login.php:114
|
||||
#: includes/class-m365-login.php:122
|
||||
msgid "M365 Login requires the PHP OpenSSL extension (needed to verify Microsoft token signatures and to encrypt the client secret)."
|
||||
msgstr "M365 Login benötigt die PHP-Erweiterung OpenSSL (zur Prüfung der Microsoft-Token-Signaturen und zur Verschlüsselung des Client Secrets)."
|
||||
|
||||
|
|
|
|||
|
|
@ -14,445 +14,587 @@ msgstr ""
|
|||
"X-Generator: bin/make-pot.py\n"
|
||||
"X-Domain: m365-login\n"
|
||||
|
||||
#: includes/class-m365-login-admin.php:63 includes/class-m365-login-admin.php:64 includes/class-m365-login-admin.php:203
|
||||
#: includes/class-m365-login-admin.php:75 includes/class-m365-login-admin.php:76 includes/class-m365-login-admin.php:248
|
||||
msgid "M365 Login"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:102
|
||||
#: includes/class-m365-login-admin.php:114
|
||||
msgid "M365 Login is active but not connected to Microsoft Entra ID yet."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:104
|
||||
#: includes/class-m365-login-admin.php:116
|
||||
msgid "Open the settings"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:132
|
||||
#: includes/class-m365-login-admin.php:145
|
||||
msgid "Choose button icon"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:133
|
||||
#: includes/class-m365-login-admin.php:146
|
||||
msgid "Use this icon"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:134
|
||||
#: includes/class-m365-login-admin.php:147
|
||||
msgid "Copied!"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:135 includes/class-m365-login-admin.php:427
|
||||
#: includes/class-m365-login-admin.php:148 includes/class-m365-login-admin.php:502 includes/class-m365-login-admin.php:545
|
||||
msgid "Copy"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:136
|
||||
#: includes/class-m365-login-admin.php:149
|
||||
msgid "Testing…"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:137
|
||||
#: includes/class-m365-login-admin.php:150
|
||||
msgid "The tenant could not be reached. Check the tenant ID and the server’s outgoing connections."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:149
|
||||
#: includes/class-m365-login-admin.php:151
|
||||
msgid "No groups found."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:152
|
||||
msgid "Searching…"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:153
|
||||
msgid "Add"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:154 includes/class-m365-login-admin.php:476
|
||||
msgid "Remove"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:155 includes/class-m365-login-admin.php:210 includes/class-m365-login-admin.php:461
|
||||
msgid "Save the connection settings first, then search for groups."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:156
|
||||
msgid "Generate a new fallback key on save? The old link stops working."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:168 includes/class-m365-login-admin.php:207
|
||||
msgid "You are not allowed to do this."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:154
|
||||
#: includes/class-m365-login-admin.php:173
|
||||
msgid "Please enter a valid tenant ID first."
|
||||
msgstr ""
|
||||
|
||||
#. translators: %d: HTTP status code
|
||||
#: includes/class-m365-login-admin.php:168
|
||||
#: includes/class-m365-login-admin.php:187
|
||||
msgid "Microsoft answered with HTTP %d. Is the tenant ID correct?"
|
||||
msgstr ""
|
||||
|
||||
#. translators: %d: HTTP status code
|
||||
#: includes/class-m365-login-admin.php:177
|
||||
#: includes/class-m365-login-admin.php:196
|
||||
msgid "Tenant reachable. The OpenID configuration was loaded successfully."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:187
|
||||
msgid "You are not allowed to access this page."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:204
|
||||
msgid "Let existing users sign in with their Microsoft 365 / Entra ID account."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:209
|
||||
msgid "Connected"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:209
|
||||
msgid "Setup incomplete"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:217
|
||||
msgid "Connection"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:218
|
||||
msgid "Button"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:219
|
||||
msgid "Security"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:228
|
||||
msgid "Microsoft Entra ID app registration"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:229
|
||||
msgid "Enter the values from your app registration in the Microsoft Entra admin center."
|
||||
msgid "Microsoft Graph refused the request. Grant the application permission \"GroupMember.Read.All\" (or \"Directory.Read.All\") with admin consent in Entra ID."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:232
|
||||
msgid "Directory (tenant) ID"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:235
|
||||
msgid "Test tenant"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:237
|
||||
msgid "Recommended: the GUID of your tenant. Only sign-ins from this tenant are accepted. \"organizations\" allows any work or school account."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:242
|
||||
msgid "Application (client) ID"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:247
|
||||
msgid "Client secret"
|
||||
msgid "You are not allowed to access this page."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:249
|
||||
msgid "•••••••••••• (stored, leave empty to keep)"
|
||||
msgid "Let existing users sign in with their Microsoft 365 / Entra ID account."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:249
|
||||
msgid "Paste the secret value"
|
||||
#: includes/class-m365-login-admin.php:254
|
||||
msgid "Connected"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:250
|
||||
msgid "Show secret"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:255
|
||||
msgid "Remove the stored secret"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:258
|
||||
msgid "Stored encrypted (AES-256-GCM, key derived from your WordPress salts) and never displayed again. Client secrets expire – note the expiry date in Entra ID."
|
||||
#: includes/class-m365-login-admin.php:254
|
||||
msgid "Setup incomplete"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:262
|
||||
msgid "Account prompt"
|
||||
msgid "Connection"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:263
|
||||
msgid "Button"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:264
|
||||
msgid "Always let the user pick an account (recommended)"
|
||||
msgid "Security"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:265
|
||||
msgid "Use the current Microsoft session if available"
|
||||
#: includes/class-m365-login-admin.php:273
|
||||
msgid "Microsoft Entra ID app registration"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:266
|
||||
msgid "Always require re-entering credentials"
|
||||
#: includes/class-m365-login-admin.php:274
|
||||
msgid "Enter the values from your app registration in the Microsoft Entra admin center."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:275
|
||||
msgid "Appearance"
|
||||
#: includes/class-m365-login-admin.php:277
|
||||
msgid "Directory (tenant) ID"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:278
|
||||
msgid "Live preview"
|
||||
#: includes/class-m365-login-admin.php:280
|
||||
msgid "Test tenant"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:282
|
||||
msgid "Recommended: the GUID of your tenant. Only sign-ins from this tenant are accepted. \"organizations\" allows any work or school account."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:287
|
||||
msgid "Application (client) ID"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:292
|
||||
msgid "Button text"
|
||||
msgid "Client secret"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:296
|
||||
msgid "Divider text"
|
||||
#: includes/class-m365-login-admin.php:294
|
||||
msgid "•••••••••••• (stored, leave empty to keep)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:298
|
||||
msgid "Leave empty to hide the divider line."
|
||||
#: includes/class-m365-login-admin.php:294
|
||||
msgid "Paste the secret value"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:295
|
||||
msgid "Show secret"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:300
|
||||
msgid "Remove the stored secret"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:303
|
||||
msgid "Icon"
|
||||
msgid "Stored encrypted (AES-256-GCM, key derived from your WordPress salts) and never displayed again. Client secrets expire – note the expiry date in Entra ID."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:306
|
||||
msgid "Show an icon on the button"
|
||||
#: includes/class-m365-login-admin.php:307
|
||||
msgid "Account prompt"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:317
|
||||
msgid "Default: Microsoft logo"
|
||||
#: includes/class-m365-login-admin.php:309
|
||||
msgid "Always let the user pick an account (recommended)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:319
|
||||
msgid "Choose from media library"
|
||||
#: includes/class-m365-login-admin.php:310
|
||||
msgid "Use the current Microsoft session if available"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:311
|
||||
msgid "Always require re-entering credentials"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:320
|
||||
msgid "Use Microsoft logo"
|
||||
msgid "Appearance"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:322
|
||||
msgid "PNG, SVG, JPG or WebP. Square images (e.g. 64×64 px) work best."
|
||||
#: includes/class-m365-login-admin.php:323
|
||||
msgid "Live preview"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:330
|
||||
msgid "Background"
|
||||
#: includes/class-m365-login-admin.php:337
|
||||
msgid "Button text"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:331
|
||||
msgid "Background (hover)"
|
||||
#: includes/class-m365-login-admin.php:341
|
||||
msgid "Divider text"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:332
|
||||
msgid "Text colour"
|
||||
#: includes/class-m365-login-admin.php:343
|
||||
msgid "Leave empty to hide the divider line."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:333
|
||||
msgid "Border"
|
||||
#: includes/class-m365-login-admin.php:348
|
||||
msgid "Icon"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:346
|
||||
msgid "Corner radius"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:350
|
||||
msgid "Position on the login page"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:352
|
||||
msgid "Below the login form"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:353
|
||||
msgid "Above the login form"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:359
|
||||
msgid "Quick presets"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:360
|
||||
msgid "Microsoft dark"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:361
|
||||
msgid "Microsoft light"
|
||||
#: includes/class-m365-login-admin.php:351
|
||||
msgid "Show an icon on the button"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:362
|
||||
msgid "Azure blue"
|
||||
msgid "Default: Microsoft logo"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:363
|
||||
msgid "WordPress blue"
|
||||
#: includes/class-m365-login-admin.php:364
|
||||
msgid "Choose from media library"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:371
|
||||
msgid "User matching & hardening"
|
||||
#: includes/class-m365-login-admin.php:365
|
||||
msgid "Use Microsoft logo"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:372
|
||||
msgid "Users are never created automatically. A Microsoft sign-in only succeeds when a WordPress user with the same e-mail address already exists."
|
||||
#: includes/class-m365-login-admin.php:367
|
||||
msgid "PNG, SVG, JPG or WebP. Square images (e.g. 64×64 px) work best."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:375
|
||||
msgid "Background"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:376
|
||||
msgid "Background (hover)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:377
|
||||
msgid "Bind WordPress accounts to the Microsoft object ID"
|
||||
msgid "Text colour"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:378
|
||||
msgid "On first sign-in the immutable Microsoft object ID is stored with the user. Later sign-ins with the same e-mail but a different Microsoft identity are rejected. Strongly recommended."
|
||||
msgid "Border"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:385
|
||||
msgid "Fall back to the user principal name (UPN)"
|
||||
#: includes/class-m365-login-admin.php:391
|
||||
msgid "Corner radius"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:386
|
||||
msgid "If the token contains no \"email\" claim, use the UPN (e.g. jane@contoso.com) when it is a valid e-mail address. Usually required for work accounts."
|
||||
#: includes/class-m365-login-admin.php:395
|
||||
msgid "Position on the login page"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:393
|
||||
msgid "Keep users signed in (\"Remember me\")"
|
||||
#: includes/class-m365-login-admin.php:397
|
||||
msgid "Below the login form"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:394
|
||||
msgid "Issues a 14-day WordPress session instead of a browser session."
|
||||
#: includes/class-m365-login-admin.php:398
|
||||
msgid "Above the login form"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:399
|
||||
msgid "Allowed e-mail domains (optional)"
|
||||
#: includes/class-m365-login-admin.php:404
|
||||
msgid "Quick presets"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:401
|
||||
msgid "One or more domains separated by commas or new lines. Leave empty to allow any domain of your tenant."
|
||||
#: includes/class-m365-login-admin.php:405
|
||||
msgid "Microsoft dark"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:406
|
||||
msgid "What the plugin does to keep sign-ins safe"
|
||||
msgid "Microsoft light"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:407
|
||||
msgid "Azure blue"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:408
|
||||
msgid "OpenID Connect authorization code flow with PKCE (S256) – no tokens ever pass through the browser."
|
||||
msgid "WordPress blue"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:409
|
||||
msgid "Single-use state and nonce values bound to the browser via an HttpOnly cookie (CSRF and replay protection)."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:410
|
||||
msgid "ID token signature verified against Microsoft’s published signing keys; issuer, audience, tenant, expiry and nonce are checked."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:411
|
||||
msgid "Client secret encrypted at rest; no accounts are created, no passwords are changed."
|
||||
#: includes/class-m365-login-admin.php:416
|
||||
msgid "User matching & hardening"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:417
|
||||
msgid "Save changes"
|
||||
msgid "Users are never created automatically. A Microsoft sign-in only succeeds when a WordPress user with the same e-mail address already exists."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:422
|
||||
msgid "Bind WordPress accounts to the Microsoft object ID"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:423
|
||||
msgid "Redirect URI"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:424
|
||||
msgid "Register this URI in your app registration under Authentication → Web → Redirect URIs:"
|
||||
msgid "On first sign-in the immutable Microsoft object ID is stored with the user. Later sign-ins with the same e-mail but a different Microsoft identity are rejected. Strongly recommended."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:430
|
||||
msgid "Plain permalinks are active, so the callback uses a query string. If you enable pretty permalinks later, the redirect URI changes and must be updated in Entra ID."
|
||||
msgid "Fall back to the user principal name (UPN)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:433
|
||||
msgid "Your site does not use HTTPS. Microsoft only accepts http:// redirect URIs for localhost; production sites must use HTTPS."
|
||||
#: includes/class-m365-login-admin.php:431
|
||||
msgid "If the token contains no \"email\" claim, use the UPN (e.g. jane@contoso.com) when it is a valid e-mail address. Usually required for work accounts."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:438
|
||||
msgid "Setup in 5 steps"
|
||||
msgid "Keep users signed in (\"Remember me\")"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:440
|
||||
msgid "Open the Microsoft Entra admin center → App registrations → New registration."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:441
|
||||
msgid "Choose \"Accounts in this organizational directory only\", set the platform to Web and paste the redirect URI above."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:442
|
||||
msgid "Copy the Application (client) ID and Directory (tenant) ID from the overview page."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:443
|
||||
msgid "Under Certificates & secrets create a client secret and copy its value (not the ID)."
|
||||
#: includes/class-m365-login-admin.php:439
|
||||
msgid "Issues a 14-day WordPress session instead of a browser session."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:444
|
||||
msgid "Under Token configuration add the optional claim \"email\" for ID tokens (recommended), then save this page."
|
||||
msgid "Allowed e-mail domains (optional)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:446
|
||||
msgid "Required API permission: openid, profile, email (delegated) – granted by default."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:450
|
||||
msgid "Shortcode"
|
||||
msgid "One or more domains separated by commas or new lines. Leave empty to allow any domain of your tenant."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:451
|
||||
msgid "Allowed Entra groups (optional)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:452
|
||||
msgid "Only members of at least one of these groups may sign in. Leave empty to allow every matched user. Nested memberships count."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:455
|
||||
msgid "Search groups"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:457
|
||||
msgid "Type a group name or paste an object ID…"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:458
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:463
|
||||
msgid "Needs the application permission \"GroupMember.Read.All\" with admin consent. Without it you can still paste group object IDs."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:469
|
||||
msgid "Selected groups"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:470
|
||||
msgid "No groups selected – every matched user may sign in."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:480
|
||||
msgid "Membership is read from the \"groups\" claim of the ID token when present; otherwise the plugin asks Microsoft Graph (application permission \"User.Read.All\" or \"Directory.Read.All\"). If neither works, the sign-in is refused."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:485
|
||||
msgid "Button-only mode"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:486
|
||||
msgid "Hide the username/password form and the \"Lost your password?\" link, and refuse password sign-ins on the login page. Application passwords, REST and XML-RPC are not affected."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:491
|
||||
msgid "Show only the Microsoft button on the login page"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:492
|
||||
msgid "Becomes active once the connection is configured. Make sure your own account can sign in via Microsoft before enabling this."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:497
|
||||
msgid "Fallback link (keep it secret)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:498
|
||||
msgid "Opening this link shows the password form again in that browser for 30 minutes. Bookmark it somewhere safe – it is your way back in if Microsoft sign-in ever breaks."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:506
|
||||
msgid "Generate a new key when saving"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:509
|
||||
msgid "A key is generated automatically the first time you save these settings."
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: PHP constant
|
||||
#: includes/class-m365-login-admin.php:515
|
||||
msgid "Emergency switch: add %s to wp-config.php to disable button-only mode entirely."
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: PHP constant
|
||||
#: includes/class-m365-login-admin.php:524
|
||||
msgid "What the plugin does to keep sign-ins safe"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:526
|
||||
msgid "OpenID Connect authorization code flow with PKCE (S256) – no tokens ever pass through the browser."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:527
|
||||
msgid "Single-use state and nonce values bound to the browser via an HttpOnly cookie (CSRF and replay protection)."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:528
|
||||
msgid "ID token signature verified against Microsoft’s published signing keys; issuer, audience, tenant, expiry and nonce are checked."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:529
|
||||
msgid "Client secret encrypted at rest; no accounts are created, no passwords are changed."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:535
|
||||
msgid "Save changes"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:541
|
||||
msgid "Redirect URI"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:542
|
||||
msgid "Register this URI in your app registration under Authentication → Web → Redirect URIs:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:548
|
||||
msgid "Plain permalinks are active, so the callback uses a query string. If you enable pretty permalinks later, the redirect URI changes and must be updated in Entra ID."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:551
|
||||
msgid "Your site does not use HTTPS. Microsoft only accepts http:// redirect URIs for localhost; production sites must use HTTPS."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:556
|
||||
msgid "Setup in 5 steps"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:558
|
||||
msgid "Open the Microsoft Entra admin center → App registrations → New registration."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:559
|
||||
msgid "Choose \"Accounts in this organizational directory only\", set the platform to Web and paste the redirect URI above."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:560
|
||||
msgid "Copy the Application (client) ID and Directory (tenant) ID from the overview page."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:561
|
||||
msgid "Under Certificates & secrets create a client secret and copy its value (not the ID)."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:562
|
||||
msgid "Under Token configuration add the optional claim \"email\" for ID tokens (recommended), then save this page."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:564
|
||||
msgid "Required API permission: openid, profile, email (delegated) – granted by default."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:565
|
||||
msgid "Optional, for group restrictions: application permissions GroupMember.Read.All and User.Read.All (Microsoft Graph) with admin consent."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:569
|
||||
msgid "Shortcode"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-admin.php:570
|
||||
msgid "Place the button on a custom login page:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-auth.php:593
|
||||
#: includes/class-m365-login-auth.php:136
|
||||
msgid "Password sign-in is disabled on this site. Please use the Microsoft button."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-auth.php:751
|
||||
msgid "Password sign-in is temporarily enabled for this browser (30 minutes)."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-auth.php:758 includes/class-m365-login-graph.php:63
|
||||
msgid "Microsoft login is not configured yet."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-auth.php:594
|
||||
#: includes/class-m365-login-auth.php:759
|
||||
msgid "The login request expired or was invalid. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-auth.php:595
|
||||
#: includes/class-m365-login-auth.php:760
|
||||
msgid "Microsoft sign-in was cancelled."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-auth.php:596
|
||||
#: includes/class-m365-login-auth.php:761
|
||||
msgid "Microsoft returned an error. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-auth.php:597
|
||||
#: includes/class-m365-login-auth.php:762
|
||||
msgid "Could not complete the sign-in with Microsoft. Please try again or contact an administrator."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-auth.php:598
|
||||
#: includes/class-m365-login-auth.php:763
|
||||
msgid "The Microsoft sign-in could not be verified."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-auth.php:599
|
||||
#: includes/class-m365-login-auth.php:764
|
||||
msgid "Your Microsoft account did not provide an e-mail address."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-auth.php:600
|
||||
#: includes/class-m365-login-auth.php:765
|
||||
msgid "Your e-mail domain is not allowed to sign in here."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-auth.php:601
|
||||
#: includes/class-m365-login-auth.php:766
|
||||
msgid "No WordPress account exists for your Microsoft e-mail address."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-auth.php:602
|
||||
#: includes/class-m365-login-auth.php:767
|
||||
msgid "This WordPress account is linked to a different Microsoft account. Please contact an administrator."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-auth.php:603
|
||||
#: includes/class-m365-login-auth.php:768
|
||||
msgid "You are not allowed to sign in with this account."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-settings.php:40
|
||||
#: includes/class-m365-login-auth.php:769
|
||||
msgid "Your Microsoft account is not a member of a group that is allowed to sign in here."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-auth.php:770
|
||||
msgid "Your group membership could not be verified. Please contact an administrator."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-auth.php:771
|
||||
msgid "The fallback key is not valid."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-auth.php:772
|
||||
msgid "Too many attempts. Please wait 15 minutes."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-graph.php:193
|
||||
msgid "Group"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-graph.php:195
|
||||
msgid "Security group"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-graph.php:197
|
||||
msgid "Microsoft 365 group"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-settings.php:44
|
||||
msgid "Sign in with Microsoft"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-settings.php:49
|
||||
#: includes/class-m365-login-settings.php:53
|
||||
msgid "or"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-settings.php:176
|
||||
#: includes/class-m365-login-settings.php:245
|
||||
msgid "The tenant ID must be a GUID (e.g. 1a2b3c4d-…) or one of \"organizations\", \"common\", \"consumers\"."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-settings.php:184
|
||||
#: includes/class-m365-login-settings.php:253
|
||||
msgid "The application (client) ID must be a GUID."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-settings.php:196
|
||||
#: includes/class-m365-login-settings.php:265
|
||||
msgid "The client secret contains invalid characters."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login-settings.php:200
|
||||
#: includes/class-m365-login-settings.php:269
|
||||
msgid "The client secret could not be encrypted. Is the OpenSSL extension available?"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login.php:94
|
||||
#: includes/class-m365-login.php:102
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login.php:105
|
||||
#: includes/class-m365-login.php:113
|
||||
msgid "M365 Login requires PHP 7.4 or newer."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login.php:106 includes/class-m365-login.php:115
|
||||
#: includes/class-m365-login.php:114 includes/class-m365-login.php:123
|
||||
msgid "Plugin activation failed"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-m365-login.php:114
|
||||
#: includes/class-m365-login.php:122
|
||||
msgid "M365 Login requires the PHP OpenSSL extension (needed to verify Microsoft token signatures and to encrypt the client secret)."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ define( 'M365_LOGIN_OPTION', 'm365_login_settings' );
|
|||
require_once M365_LOGIN_DIR . 'includes/class-m365-login-settings.php';
|
||||
require_once M365_LOGIN_DIR . 'includes/class-m365-login-crypto.php';
|
||||
require_once M365_LOGIN_DIR . 'includes/class-m365-login-jwt.php';
|
||||
require_once M365_LOGIN_DIR . 'includes/class-m365-login-graph.php';
|
||||
require_once M365_LOGIN_DIR . 'includes/class-m365-login-auth.php';
|
||||
require_once M365_LOGIN_DIR . 'includes/class-m365-login-button.php';
|
||||
require_once M365_LOGIN_DIR . 'includes/class-m365-login-admin.php';
|
||||
|
|
|
|||
21
readme.txt
21
readme.txt
|
|
@ -19,6 +19,8 @@ The plugin is deliberately small and strict:
|
|||
* **No user provisioning.** A Microsoft sign-in succeeds only when a WordPress user with the same e-mail address already exists. Nobody gets an account just by having a Microsoft login.
|
||||
* **Password login stays available.** The button is an additional option; the normal form is untouched.
|
||||
* **Fully customisable button.** Change the text, replace the Microsoft logo with your own icon from the media library, pick background, hover, text and border colours, adjust the corner radius, and choose whether the button appears above or below the login form – with a live preview.
|
||||
* **Entra group restriction.** Search and pick the groups whose members may sign in, right in the settings screen. Membership is checked via the ID token's `groups` claim or Microsoft Graph (nested groups included).
|
||||
* **Button-only mode.** Hide the username/password form and refuse password sign-ins on the login page. A secret fallback link (and a `wp-config.php` constant) brings the form back when you need it.
|
||||
* **Clean settings screen** with a copy-and-paste redirect URI, a tenant connectivity test and a five-step setup guide.
|
||||
* **Shortcode** `[m365_login_button]` for custom login pages.
|
||||
|
||||
|
|
@ -29,7 +31,8 @@ The plugin is deliberately small and strict:
|
|||
* The **ID token signature is verified** against Microsoft's published signing keys (JWKS, cached and refreshed on key rollover). Issuer, audience, tenant, expiry, not-before and nonce are all checked. Only RS256 is accepted.
|
||||
* Optional **tenant pinning**: when a tenant GUID is configured, tokens from any other tenant are rejected.
|
||||
* **Account binding**: on first sign-in the immutable Microsoft object ID is stored with the user; later sign-ins with the same e-mail but a different Microsoft identity are refused.
|
||||
* Optional **e-mail domain allow-list**.
|
||||
* Optional **e-mail domain allow-list** and **group allow-list** (fails closed when membership cannot be verified).
|
||||
* **Button-only mode** blocks password sign-in server-side, not just visually; the fallback key is rate limited and never stored in a cookie.
|
||||
* The **client secret is encrypted at rest** (AES-256-GCM, key derived from your WordPress salts) and never displayed again.
|
||||
* Every setting is sanitised, every output escaped, every admin request nonce- and capability-checked.
|
||||
|
||||
|
|
@ -52,6 +55,11 @@ Endpoints used (all under `https://login.microsoftonline.com/`):
|
|||
* `/{tenant}/discovery/v2.0/keys` – the server downloads Microsoft's public signing keys to verify the ID token. No user data is sent.
|
||||
* `/{tenant}/v2.0/.well-known/openid-configuration` – fetched only when an administrator clicks "Test tenant". No user data is sent.
|
||||
|
||||
When the optional **group restriction** is configured, the plugin additionally connects to **Microsoft Graph** (`https://graph.microsoft.com/v1.0/`) using an application token obtained from `/{tenant}/oauth2/v2.0/token` (client credentials, client ID and secret are sent):
|
||||
|
||||
* `/groups` – only when an administrator searches for groups in the settings screen. The typed search text is sent.
|
||||
* `/users/{id}/checkMemberGroups` – during sign-in when the ID token carries no usable `groups` claim. The user's Microsoft object ID and the configured group IDs are sent; Microsoft returns which of those groups the user belongs to.
|
||||
|
||||
The plugin receives the user's e-mail address / user principal name, display name and Microsoft object ID from Microsoft and uses them solely to find the matching WordPress account. Nothing else is stored.
|
||||
|
||||
Microsoft terms and privacy: [Microsoft Services Agreement](https://www.microsoft.com/servicesagreement), [Microsoft Privacy Statement](https://privacy.microsoft.com/privacystatement), [Microsoft identity platform documentation](https://learn.microsoft.com/entra/identity-platform/).
|
||||
|
|
@ -86,6 +94,14 @@ Set the tenant to `consumers` or `common`. Note that Microsoft does not allow qu
|
|||
|
||||
The e-mail address in the Microsoft token does not match any WordPress user. Check the user's e-mail address in WordPress, enable the UPN fallback on the Security tab, or add the `email` optional claim in the app registration.
|
||||
|
||||
= How do I restrict sign-in to certain Entra groups? =
|
||||
|
||||
Open the Security tab, search for the groups (requires the Microsoft Graph application permission `GroupMember.Read.All` with admin consent) or paste their object IDs, add them and save. During sign-in the plugin first looks at the `groups` claim of the ID token; if the app registration does not emit one (or the user is in more than 200 groups) it asks Microsoft Graph (`User.Read.All`). If membership cannot be verified, the sign-in is refused.
|
||||
|
||||
= How do I get back in when button-only mode is on and Microsoft sign-in is broken? =
|
||||
|
||||
Open the fallback link shown on the Security tab (`wp-login.php?m365_fallback=KEY`); the password form is shown again in that browser for 30 minutes. Without the link, add `define( 'M365_LOGIN_DISABLE_BUTTON_ONLY', true );` to `wp-config.php` or rename the plugin folder via FTP.
|
||||
|
||||
= Does it work with custom login pages? =
|
||||
|
||||
Yes, use the shortcode `[m365_login_button redirect="/dashboard/"]`.
|
||||
|
|
@ -103,7 +119,8 @@ The settings, cached data and the per-user Microsoft object ID are removed.
|
|||
1. The customised button on the WordPress login screen.
|
||||
2. Settings – Connection tab with redirect URI and tenant test.
|
||||
3. Settings – Button tab with live preview, colour pickers and icon picker.
|
||||
4. Settings – Security tab.
|
||||
4. Settings – Security tab with the Entra group picker and button-only mode.
|
||||
5. Login screen in button-only mode.
|
||||
|
||||
== Changelog ==
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue