Commit graph

16 commits

Author SHA1 Message Date
Johannes Rauh
3c199b725b Use Generator to yield two-factor methods 2026-05-27 10:47:21 +02:00
Johannes Rauh
5cd6a2b90d Guard hook method call sites against implementation exceptions
The `TwoFactor` interface methods that contain logic are implemented by module
developers and may contain arbitrary third-party code. Without guards at the
call sites, an uncaught exception from a faulty implementation could crash the
whole application.

Each guard logs the exception with a confidential trace. `isEnrolled()` in
`loadEnrolled()` rethrows so the caller aborts rather than silently continuing.
`verify()` and `assembleEnrollmentFormElements()` show a user-facing message
and call `onError()` to keep the user on the form.

The existing `enroll()` and `unenroll()` catches are also updated: two separate
`Logger::error()` calls become one, and the user-visible messages gain
translation context.
2026-05-27 10:47:21 +02:00
Johannes Rauh
df4348081f Deduplicate remember-me revocation and redirect in onSuccess()
Both the enroll and unenroll paths ended with the same two statements.
Moving them after the switch eliminates the duplication.
2026-05-27 10:47:21 +02:00
Johannes Rauh
524cf5c286 Revoke remember-me cookies on unenrollment
Cookies issued while 2FA was active retain their validity after unenrollment
without this change. Revoking them forces a fresh login and prevents sessions
that were established under the enrolled requirement from persisting in a state
where that requirement no longer applies.
2026-05-27 10:47:21 +02:00
Johannes Rauh
dd1f570629 Catch and surface exceptions from enroll() and unenroll()
Previously both methods were declared to throw only `ConfigurationError`
and callers did not catch it. This removes the now-overly-narrow
`@throws ConfigurationError` declarations and wraps both call sites in
`try/catch(Throwable)`, letting the enrollment form log the full
confidential trace and show the exception message inline rather than
leaving the user with an unhandled error page.

The enroll false path is also updated: `Notification::error()` is
replaced with `$this->onError()` to keep error feedback consistent with
the exception path.
2026-05-27 10:47:21 +02:00
Johannes Rauh
240141a412 Remove success notifications 2026-05-27 10:47:21 +02:00
Johannes Rauh
efed65fb09 Disable the placeholder option in the 2FA method select
The empty placeholder `' - Please choose - '` could be re-selected after a real
method had been picked, resulting in `TwoFactorHook::fromName()` searching for
an empty string. Setting `disabledOptions` to `['']` prevents that by making the
placeholder unselectable once the user has moved past it.
2026-05-27 10:47:21 +02:00
Johannes Rauh
40a251d413 Require 2FA method selection and drop null fallback in onSuccess
With `required` set, the form will not pass validation when no method is
selected, so `getValue(static::METHOD)` in `onSuccess` can never return null.
The `?? ''` fallback passed to `TwoFactorHook::fromName` is dead code once
form validation runs.
2026-05-27 10:47:21 +02:00
Johannes Rauh
f7a38cb276 Apply fieldset module class after assembleEnrollmentFormElements
Setting the `icinga-module module-{name}` class on the fieldset before calling
`assembleEnrollmentFormElements` let hook implementations overwrite or clear it
by calling `setAttributes` on the fieldset themselves. Moving the assignment to
after assembly ensures the CSS scoping class is always present regardless of
what the hook does.

The guard on the fieldset name ensures a hook implementation cannot change the
fieldset name during assembly, which would break form submission handling.
2026-05-27 10:47:21 +02:00
Johannes Rauh
db36a68c4e Early return if no method is chosen
Instead of searching for a method named `''`.
2026-05-27 10:47:21 +02:00
Johannes Rauh
4d91eda501 Add module class to 2FA config fieldset in TwoFactorEnrollmentForm
The `icinga-module module-{name}` class pair lets the providing module scope
its CSS to its own fieldset without affecting other form elements.
2026-05-27 10:47:21 +02:00
Johannes Rauh
b1ced8769a Revoke remember-me cookies on 2FA enrollment
Add `RememberMe::removeAllByUsername()` which deletes all rows from
`icingaweb_rememberme` for a given user. Call it from
`TwoFactorEnrollmentForm::onSuccess()` after a successful enroll so that
pre-enrollment cookies on other devices cannot bypass the newly required
second factor.
2026-05-27 10:47:21 +02:00
Johannes Rauh
1b90b6c14f Add enrollment UI and two-factor tab to account navigation
`TwoFactorController::configAction()` renders `TwoFactorEnrollmentForm`, which
shows a method-selector dropdown (autosubmit), the method-specific fieldset
contributed by `assembleEnrollmentFormElements()`, and either an "Enroll" or
"Unenroll" submit button depending on current enrollment state.

`AccountController`, `MyDevicesController`, and `NavigationController` each gain
a "Two-Factor Auth" tab.
2026-05-27 10:47:21 +02:00
Eric Lippmann
662de28f85 License source files as GPL-3.0-or-later
Add SPDX license headers and mark source files as GPL-3.0-or-later to
preserve the option to relicense under later GPL versions.
2026-03-26 17:49:26 +01:00
Eric Lippmann
c899456d93 Fix copyright year in ChangePasswordForm
refs #10616
2016-08-04 16:11:43 +02:00
Eric Lippmann
e62d94209f Allow users to change their password if backend is db
refs #10616
2016-07-21 17:38:19 +02:00