- Privileged accounts: the UPN rule also applies when bind_oid is off or
the account is not bound; privileges are checked on every site of a
multisite user, include code/HTML capabilities (unfiltered_html,
plugins, themes, users) and the remembered roles of deactivated
accounts.
- send_auth_cookies protection also works on WordPress 6.0/6.1.
- Run lock via INSERT IGNORE (atomic), refreshed during long runs; a
shutdown handler reports fatal errors and frees the lock.
- Deprovisioning only for accounts linked in the current tenant (tenant
recorded per account; legacy links not found are left alone).
- Safety stop based on the accounts linked before the run; new safety
stop for removals of administrative roles.
- Disable is idempotent; row-action nonces are bound to the state.
- Profile photos are re-encoded to 240 px (drops EXIF and appended
data), size-limited while downloading, removed on deactivation;
index.php guard in the photo folder.
- Privacy exporter and eraser for the copied data.
- One-time migration hardens accounts deactivated by 1.0 and cleans a
stored certificate bundle; the .cer download is always re-exported.
- Password fields hidden in button-only mode even when the connection
is broken; settings written non-autoloaded; robust user ID queries.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Four-part audit (OIDC/JWT/crypto, user sync, admin UI, login bypasses)
with dynamic PoCs against a real WordPress install; every fix is covered
by a regression test. Report: docs/security-audit.md, section 6.
Critical/High
- Multisite: settings, AJAX actions and certificate download require
manage_network_options (site admins could sign in as super admin).
- Privileged accounts are only linked (sync and first sign-in) via a
matching UPN of a member account, never via the settable mail
attribute; the sync never changes their e-mail address; e-mail change
notifications stay on.
- Button-only mode exempts by credential (application passwords, WP-CLI)
instead of request context, closing bypasses through xmlrpc.php and
REST login handlers; API requests never receive login cookies.
- Multi-tenant mode refuses guest/external identities.
Medium/Low
- Same message for right and wrong passwords; button-only no longer
switches off when the connection breaks; server-side fallback cookie
expiry; correct fallback key beats IP lockouts; right-most proxy hop;
higher start limit; one object ID per account.
- Deactivation sets a random password, revokes application passwords and
removes the role (restored on reactivation); disabled people are
deactivated even when their mail vanished; duplicate bindings handled.
- Sync: abort on empty directory answer, no deprovisioning right after a
tenant change, atomic run lock, strict photo path validation.
- Certificates: key bundles refused, clean re-exported certificate.
- Array-safe sanitising, encoded redirect_to, per-action nonces, escaped
role lists, no Graph sleeps during sign-in, warnings for public groups,
multi-tenant group rules and missing salts, uninstall clears the token.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
New "User sync" tab that imports Microsoft 365 / Entra ID users as
WordPress accounts and keeps them up to date:
- Scope: whole tenant or the (nested) members of selected groups,
guests optional, e-mail domain allow-list respected. Existing accounts
are linked by e-mail address.
- Roles: selectable default role plus a group -> role mapping (in
addition to or instead of the default role, first match wins).
Roles of pre-existing accounts are only managed on request.
- Profile: selectable Graph attributes (names, job title, department,
phones, address, language, ...) and the profile photo as avatar.
- Deprovisioning: accounts disabled or deleted in Microsoft 365 (or
removed from the sync groups) are deactivated or deleted; accounts
deactivated by the sync are reactivated automatically. Deactivated
accounts lose every sign-in path and all sessions.
- Safeguards: dry run, safety stop above 20 % (min. 5) deprovisioning,
abort on any Graph error, "deleted" only on a 404 for the object ID,
protected pre-existing administrators and own account, content
reassignment required for deletion, run lock.
- Runs manually, via WP-Cron or `wp m365-login sync [--dry-run]`.
- Users screen column with deactivate/reactivate row actions and a
read-only Microsoft 365 section on the profile screen.
The Graph client gains paging, retry on throttling and user, group
member and photo endpoints. The group picker is now reusable.
Version 1.1.0, German translations (du/Sie), docs and audit addendum.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adds a WordPress plugin that places a customisable "Sign in with
Microsoft" button on wp-login.php and signs existing users in via the
OpenID Connect authorization code flow with PKCE. Users are matched by
e-mail address only; no accounts are created.
Security: single-use state/nonce bound to an HttpOnly cookie, ID token
signature verification against Microsoft's JWKS (RS256 only) with
issuer/audience/tenant/expiry/nonce checks, optional tenant pinning,
account binding to the Microsoft object ID, e-mail domain allow-list,
client secret encrypted at rest (AES-256-GCM).
Admin: settings screen with connection, button and security tabs, live
button preview, colour presets, media-library icon picker, redirect URI
copy button and tenant connectivity test.
Packaging for WordPress.org: readme.txt with External services section,
GPL-2.0 license, uninstall.php, POT + German translations, .distignore,
build script, PHPCS config and CI running Plugin Check.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJxAHYdMfKPoN4koRc4Ci2