wp-m365-login/CHANGELOG.md
Friederich Loheide 850f0dcd54
Some checks are pending
CI / PHP lint (7.4) (pull_request) Waiting to run
CI / PHP lint (8.0) (pull_request) Waiting to run
CI / PHP lint (8.1) (pull_request) Waiting to run
CI / PHP lint (8.2) (pull_request) Waiting to run
CI / PHP lint (8.3) (pull_request) Waiting to run
CI / PHP lint (8.4) (pull_request) Waiting to run
CI / WordPress Coding Standards (pull_request) Waiting to run
CI / WordPress.org Plugin Check (pull_request) Waiting to run
Fix the findings of a full second security audit
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>
2026-09-23 17:10:30 +00:00

6.8 KiB
Raw Permalink Blame History

Changelog

All notable changes to this project are documented in this file. The format follows Keep a Changelog and the plugin adheres to Semantic Versioning.

[1.1.0] 2026-09-23

Added

  • User sync (new "User sync" tab): imports Microsoft 365 / Entra ID users as WordPress accounts the whole tenant or the (nested) members of selected groups, guests optional and links existing accounts by e-mail address.

  • Default role plus group → role mapping with a Graph-powered group picker; mapped roles either added to or replacing the default role (first match wins, reorderable). Roles of accounts that existed before the sync are only managed on request.

  • Selectable profile attributes (display name, first/last name, job title, department, company, office, employee ID, phones, address, language) and the profile photo, which replaces the Gravatar.

  • Profile photos follow Microsoft 365 on every run: versions are compared via Graph $batch (20 users per request), changed photos are downloaded again (old file deleted, new URL), photos deleted in Microsoft 365 are deleted in WordPress. Errors never delete a photo.

  • Deselected profile fields (m365_*) and photos are removed from the profiles on the next run; fields cleared in Microsoft 365 are cleared in WordPress; a user's photo is deleted together with the user.

  • Deactivation or deletion of WordPress accounts whose Microsoft 365 account was disabled, deleted or removed from the sync groups; automatic reactivation. Deactivated accounts cannot sign in at all (Microsoft, password, application passwords) and lose all sessions.

  • Safeguards: dry run, safety stop above 20 % deprovisioning (at least 5 accounts), abort on any Graph error, deletion only on a 404 for the object ID, protected pre-existing administrators and own account, content reassignment required for deletion, run lock.

  • Scheduled sync via WP-Cron (hourly, twice daily, daily), wp m365-login sync [--dry-run], report of the last run in the settings.

  • "Microsoft 365" column and deactivate/reactivate row actions on the users screen; read-only Microsoft 365 section on the profile screen.

  • Graph client: paging, retry on throttling (429/503/504), user, group member and photo endpoints.

  • Filters and actions for the sync (m365_login_sync_*, m365_login_user_disabled, m365_login_user_enabled).

  • Excluded Entra groups (Security tab): members can never sign in with Microsoft, even when they are in an allowed group. Checked via the groups claim and always via Microsoft Graph checkMemberGroups (a filtered claim cannot prove non-membership); fails closed.

Changed

  • The group picker is reusable (security groups, sync groups, role mapping).

Security

Fixes from a full second security audit (details: docs/security-audit.md, section 6):

  • Multisite: settings, connection test, certificates and user sync require manage_network_options (a site admin could otherwise sign in as the super admin via an own tenant).
  • Administrator accounts are only linked (sync and first sign-in) through a matching user principal name of a member account, never through the freely settable mail attribute; their e-mail address is never changed by the sync; e-mail change notifications stay on for other accounts.
  • Button-only mode: exemption by credential (application passwords, WP-CLI) instead of request context closes bypasses through xmlrpc.php and REST login handlers of other plugins; API requests never receive login cookies; same message for right and wrong passwords; mode no longer switches off when the connection breaks; fallback cookie expires on the server; the correct fallback key works despite IP lockouts; right-most proxy header entry; start limit raised to 300.
  • Multi-tenant mode refuses guest/external identities; one Microsoft object ID can only be bound to one WordPress account.
  • Deactivation also sets a random password, revokes application passwords and removes the role (restored on reactivation); linked people disabled in Microsoft 365 are deactivated even if their e-mail vanished or changed domain; duplicate bindings are all deprovisioned.
  • Sync safety: abort on an empty directory answer, no deprovisioning in the first run after a tenant change, atomic run lock, strict photo path validation.
  • Certificates: key+certificate bundles are refused in the certificate field and only a clean re-exported certificate is stored.
  • Robust sanitising of array input, encoded redirect_to in the button URL, separate nonces per AJAX action, escaped role lists, no Graph retries/sleeps during sign-in, warnings for public Microsoft 365 groups, group rules in multi-tenant mode and salts missing from wp-config.php, cached Graph token removed on uninstall.

Fixed

  • Failed Microsoft sign-ins (token exchange, token verification, object ID mismatch, group checks) ended in a PHP fatal error because the auth component's log helper had been removed in 1.0.0 development.
  • "Generate certificate" and removing the certificate did not keep the change and encrypted a stored client secret a second time (internal settings writes ran through the form sanitiser).

[1.0.0] 2026-09-22

Added

  • "Sign in with Microsoft" button on wp-login.php (OpenID Connect authorization code flow with PKCE).
  • Matching of existing WordPress users by e-mail address (optional UPN fallback), no user provisioning.
  • Settings screen (own top-level menu entry "M365 Login") with connection, button and security tabs, live button preview, colour presets, media-library icon picker, redirect-URI copy button and tenant connectivity test.
  • ID token verification against Microsoft's JWKS (RS256, issuer, audience, tenant, expiry, nonce).
  • Encrypted client secret storage (AES-256-GCM).
  • Certificate based client authentication (RFC 7523 private_key_jwt): one-click generation of a 3072-bit RSA key pair with self-signed certificate, .cer download, own PEM upload, expiry display; step-by-step guides for both methods in the settings.
  • Security audit (docs/security-audit.md) with fixes: unverified email claim ignored in multi-tenant mode, login-start rate limit, trusted proxy IP header.
  • 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.
  • Custom login page support: automatic button in wp_login_form() forms, m365_login_button() / m365_login_messages() template functions, custom login URL for error messages, fallback link and logout redirect.
  • [m365_login_button] shortcode (with divider and messages attributes) and developer hooks.
  • German translation.