Bug 22706: Add plugin hooks for Norwegian national patron database
authorMagnus Enger <magnus@libriotech.no>
Wed, 24 Apr 2019 12:59:03 +0000 (14:59 +0200)
committerMartin Renvoize <martin.renvoize@ptfs-europe.com>
Tue, 29 Oct 2019 12:19:49 +0000 (12:19 +0000)
The main point of this patch is to make it possible to integrate Koha
with the Norwegian national patron database (NNPDB). Code for this was
earlier introduced in Bug 11401 and removed again in Bug 21068.

To test this is mainly a question of spotting regressions, it should
still be possible to set and change a password in all possible ways:
- Setting a password for a new user
- Changing a password in the staff client
- Changing a password in the OPAC
If these work as expected, everything should be OK.

A nice side effect of this work is that it will allow for plugins that
validate passwords. I have created a tiny plugin that enforces PIN
codes of 4 digits. (Yeah, I know, those are the worst passwords, but
some libraries do require them.) It is published here:
https://github.com/Libriotech/koha-plugin-pin
To test this way, install the plugin and try to change the password
of an exsisting user to something that is not a 4 digit PIN. You
should get an error that says "The password was rejected by a plugin".

Signed-off-by: Brendan Gallagher <brendan@bywatersolutions.com>

Updated 2019-10-23:
- Moved the plugin checks to before the call to $self->SUPER::store to
  make sure patrons are not saved if the password fails a plugin check
- Made the plugin checks in set_password respect skip_validation while
  retaining the functionality for NNPDB

Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>

Koha/Exceptions/Password.pm
Koha/Patron.pm
koha-tmpl/intranet-tmpl/prog/en/modules/members/member-password.tt
members/member-password.pl

index c7203c8..99315d9 100644 (file)
@@ -38,7 +38,11 @@ use Exception::Class (
     'Koha::Exceptions::Password::WhitespaceCharacters' => {
         isa => 'Koha::Exceptions::Password',
         description => 'Password contains leading/trailing whitespace character(s)'
-    }
+    },
+    'Koha::Exceptions::Password::Plugin' => {
+        isa => 'Koha::Exceptions::Password',
+        description => 'The password was rejected by a plugin'
+    },
 );
 
 sub full_message {
index 6f88023..4b8e135 100644 (file)
@@ -43,6 +43,8 @@ use Koha::Patron::HouseboundRole;
 use Koha::Patron::Images;
 use Koha::Patron::Relationships;
 use Koha::Patrons;
+use Koha::Plugins;
+use Koha::Plugins::Handler;
 use Koha::Subscription::Routinglists;
 use Koha::Token;
 use Koha::Virtualshelves;
@@ -223,10 +225,35 @@ sub store {
                   :                                                   undef;
                 $self->privacy($default_privacy);
 
-
                 # Make a copy of the plain text password for later use
                 $self->plain_text_password( $self->password );
 
+                logaction( "MEMBERS", "CREATE", $self->borrowernumber, "" )
+                  if C4::Context->preference("BorrowersLog");
+
+                if ( C4::Context->preference('UseKohaPlugins') && C4::Context->config("enable_plugins") ) {
+                    # Call any check_password plugins
+                    my @plugins = Koha::Plugins->new()->GetPlugins({
+                        method => 'check_password',
+                    });
+                    foreach my $plugin ( @plugins ) {
+                        # This plugin hook will also be used by a plugin for the Norwegian national
+                        # patron database. This is why we need to pass both the password and the
+                        # borrowernumber to the plugin.
+                        my $ret = Koha::Plugins::Handler->run({
+                            class  => ref $plugin,
+                            method => 'check_password',
+                            params => {
+                                password       => $self->plain_text_password,
+                                borrowernumber => $self->borrowernumber,
+                            },
+                        });
+                        if ( $ret->{'error'} == 1 ) {
+                            Koha::Exceptions::Password::Plugin->throw();
+                        }
+                    }
+                }
+
                 # Create a disabled account if no password provided
                 $self->password( $self->password
                     ? Koha::AuthUtils::hash_password( $self->password )
@@ -238,8 +265,6 @@ sub store {
 
                 $self->add_enrolment_fee_if_needed(0);
 
-                logaction( "MEMBERS", "CREATE", $self->borrowernumber, "" )
-                  if C4::Context->preference("BorrowersLog");
             }
             else {    #ModMember
 
@@ -689,6 +714,8 @@ Exceptions are thrown if the password is not good enough.
 
 =item Koha::Exceptions::Password::TooWeak
 
+=item Koha::Exceptions::Password::Plugin (if a "check password" plugin is enabled)
+
 =back
 
 =cut
@@ -719,6 +746,32 @@ sub set_password {
         }
     }
 
+    if ( C4::Context->preference('UseKohaPlugins') && C4::Context->config("enable_plugins") ) {
+        # Call any check_password plugins
+        my @plugins = Koha::Plugins->new()->GetPlugins({
+            method => 'check_password',
+        });
+        foreach my $plugin ( @plugins ) {
+            # This plugin hook will also be used by a plugin for the Norwegian national
+            # patron database. This is why we need to pass both the password and the
+            # borrowernumber to the plugin.
+            my $ret = Koha::Plugins::Handler->run({
+                class  => ref $plugin,
+                method => 'check_password',
+                params => {
+                    password       => $password,
+                    borrowernumber => $self->borrowernumber,
+                },
+            });
+            # This plugin hook will also be used by a plugin for the Norwegian national
+            # patron database. This is why we need to call the actual plugins and then
+            # check skip_validation afterwards.
+            if ( $ret->{'error'} == 1 && !$args->{skip_validation} ) {
+                Koha::Exceptions::Password::Plugin->throw();
+            }
+        }
+    }
+
     my $digest = Koha::AuthUtils::hash_password($password);
     $self->update(
         {   password       => $digest,
index 73b95ca..e20d72e 100644 (file)
@@ -45,6 +45,9 @@
         [% IF ( ERROR_password_has_whitespaces ) %]
             <li id="ERROR_weak_password">Password must not contain leading or trailing whitespaces.</li>
         [% END %]
+        [% IF ( ERROR_from_plugin ) %]
+            <li id="ERROR_from_plugin">The password was rejected by a plugin.</li>
+        [% END %]
                [% IF ( NOPERMISSION ) %]
                <li>You do not have permission to edit this patron's login information.</li>
                [% END %]
index 256c725..c1db3aa 100755 (executable)
@@ -91,6 +91,9 @@ if ( $newpassword and not @errors) {
         elsif ( $_->isa('Koha::Exceptions::Password::TooWeak') ) {
             push @errors, 'ERROR_password_too_weak';
         }
+        elsif ( $_->isa('Koha::Exceptions::Password::Plugin') ) {
+            push @errors, 'ERROR_from_plugin';
+        }
         else {
             push( @errors, 'BADUSERID' );
         }