Bug 30724: Allow superlibrarians to manage 2FA for other patrons

This enhances the two-factor authentication management interface to allow
superlibrarians to disable 2FA for other patrons when they lose access to
their authenticator device.

Controller changes (members/two_factor_auth.pl):
* Accept borrowernumber parameter to select target patron
* Implement authorization checks (self-service or superlibrarian)
* Use proper HTTP status codes (403/404) for error conditions
* Update all patron operations to use selected patron
* Integrate with new reset_2fa() method for consistency
* Pass another_user flag to template for conditional display

Template changes (two_factor_auth.tt):
* Use has_2fa_enabled() method instead of auth_method string comparison
* Prevent superlibrarians from enabling 2FA for other users
* Show explanatory message when 2FA setup is restricted
* Maintain proper conditional display logic

UI Integration changes:
* Update members toolbar to show 2FA option for superlibrarians
* Pass borrowernumber parameter in all 2FA-related URLs
* Maintain context when canceling 2FA registration
* Use consistent parameter naming (borrowernumber vs patron_id)

To test:
1. Enable TwoFactorAuthentication system preference
2. Set up 2FA for a test patron
3. As superlibrarian, visit patron details page
4. Click 'Manage two-factor authentication' in toolbar
=> SUCCESS: 2FA management page loads for the selected patron
5. Disable 2FA for the patron
=> SUCCESS: 2FA is disabled for the target patron, not the superlibrarian
6. Verify 'Enable 2FA' button is hidden with explanatory text
=> SUCCESS: Shows message that users must enable 2FA themselves
7. Test authorization: try accessing as non-superlibrarian for different patron
=> FAIL: Returns 403 Forbidden error
8. Sign off :-D

Sponsored-by: ByWater Solutions
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is contained in:
Tomas Cohen Arazi 2025-07-31 11:11:43 -03:00 committed by Lucas Gass
parent 83ac70172b
commit 150c8c2487
No known key found for this signature in database
GPG Key ID: 60B447398B0D36CA
4 changed files with 45 additions and 26 deletions

View File

@ -26,7 +26,7 @@
</fieldset>
<fieldset class="action">
<input id="register-2FA" class="btn btn-primary" type="submit" value="Register with two-factor app" />
<a class="cancel" href="/cgi-bin/koha/members/two_factor_auth.pl">Cancel</a>
<a class="cancel" href="/cgi-bin/koha/members/two_factor_auth.pl?borrowernumber=[% patron.borrowernumber | uri %]">Cancel</a>
</fieldset>
</div>
[% END %]

View File

@ -93,8 +93,8 @@
<a class="dropdown-item disabled" aria-disabled="true" id="patronflags" href="#">Set permissions</a>
</li>
[% END %]
[% IF ( Koha.Preference('TwoFactorAuthentication') == 'enforced' || Koha.Preference('TwoFactorAuthentication') == 'enabled' ) && logged_in_user.borrowernumber == patron.borrowernumber %]
<li><a class="dropdown-item" id="twofa" href="/cgi-bin/koha/members/two_factor_auth.pl">Manage two-factor authentication</a></li>
[% IF ( Koha.Preference('TwoFactorAuthentication') == 'enforced' || Koha.Preference('TwoFactorAuthentication') == 'enabled' ) && ( logged_in_user.borrowernumber == patron.borrowernumber || logged_in_user.is_superlibrarian ) %]
<li><a class="dropdown-item" id="twofa" href="/cgi-bin/koha/members/two_factor_auth.pl?borrowernumber=[% patron.borrowernumber | uri %]">Manage two-factor authentication</a></li>
[% END %]
[% IF CAN_user_borrowers_edit_borrowers && useDischarge %]

View File

@ -46,7 +46,7 @@
[% PROCESS registration_form %]
<div id="registration-status">
<div id="registration-status-enabled" [% IF patron.auth_method != "two-factor" %]style="display: none;"[% END %]>
<div id="registration-status-enabled" [% IF !patron.has_2fa_enabled %]style="display: none;"[% END %]>
<div class="alert alert-info two-factor-status">Status: Enabled</div>
<form id="two-factor-auth" action="/cgi-bin/koha/members/two_factor_auth.pl" method="post">
@ -56,13 +56,17 @@
</form>
</div>
<div id="registration-status-disabled" [% IF patron.auth_method != "password" %]style="display: none;"[% END %]>
<div id="registration-status-disabled" [% IF patron.has_2fa_enabled %]style="display: none;"[% END %]>
<div class="alert alert-info two-factor-status">Status: Disabled</div>
[% IF Koha.Preference('TwoFactorAuthentication') == 'enforced' %]
<div>Two-factor authentication is mandatory to login. If you do not enable now it will be asked at your next login.</div>
[% END %]
[% IF another_user %]
<div>You cannot enable two-factor authentication for a user. They need to do it themselves.</div>
[% ELSE %]
[% IF Koha.Preference('TwoFactorAuthentication') == 'enforced' %]
<div>Two-factor authentication is mandatory to login. If you do not enable now it will be asked at your next login.</div>
[% END %]
<input id="enable-2FA" class="btn btn-primary" type="submit" value="Enable two-factor authentication" />
<input id="enable-2FA" class="btn btn-primary" type="submit" value="Enable two-factor authentication" />
[% END %]
</div>
</div>
[% END %]

View File

@ -42,33 +42,47 @@ if ( $TwoFactorAuthentication ne 'enabled' && $TwoFactorAuthentication ne 'enfor
exit;
}
my $op = $cgi->param('op') // '';
my $patron_id = $cgi->param('borrowernumber') // '';
my $another_user = $patron_id ne $loggedinuser;
my $logged_in_user = Koha::Patrons->find($loggedinuser);
my $op = $cgi->param('op') // '';
unless ( !$another_user || $logged_in_user->is_superlibrarian() ) {
print $cgi->redirect("/cgi-bin/koha/errors/403.pl");
exit;
}
my $patron =
$another_user
? Koha::Patrons->find($patron_id)
: $logged_in_user;
if ( !$patron ) {
print $cgi->redirect("/cgi-bin/koha/errors/404.pl");
exit;
}
if ( !C4::Context->config('encryption_key') ) {
$template->param( missing_key => 1 );
} else {
my $csrf_pars = {
session_id => scalar $cgi->cookie('CGISESSID'),
token => scalar $cgi->param('csrf_token'),
};
if ( $op eq 'cud-disable-2FA' ) {
my $auth = Koha::Auth::TwoFactorAuth->new( { patron => $logged_in_user } );
$logged_in_user->secret(undef);
$logged_in_user->auth_method('password')->store;
if ( $logged_in_user->notice_email_address ) {
$logged_in_user->queue_notice(
$patron->reset_2fa();
if ( $patron->notice_email_address ) {
$patron->queue_notice(
{
letter_params => {
module => 'members',
letter_code => '2FA_DISABLE',
branchcode => $logged_in_user->branchcode,
lang => $logged_in_user->lang,
branchcode => $patron->branchcode,
lang => $patron->lang,
tables => {
branches => $logged_in_user->branchcode,
borrowers => $logged_in_user->id
branches => $patron->branchcode,
borrowers => $patron->id
},
},
message_transports => ['email'],
@ -79,8 +93,9 @@ if ( !C4::Context->config('encryption_key') ) {
}
$template->param(
patron => $logged_in_user,
op => $op,
another_user => $another_user,
op => $op,
patron => $patron,
);
output_html_with_http_headers $cgi, $cookie, $template->output;