[Koha-patches] [PATCH] bug_16034 Koha::ExternalContent::OverDrive - a wrapper around WebService::ILS::Overdrive::Patron

Srdjan srdjan at catalyst.net.nz
Thu Mar 10 05:30:53 CET 2016


* Using the upstream module for all the heavy lifting
* opac/external/overdrive/auth.pl - 3-legged authentication handler
---
 Koha/ExternalContent.pm                         |  88 ++++++++++
 Koha/ExternalContent/OverDrive.pm               | 206 ++++++++++++++++++++++++
 Koha/Schema/Result/Borrower.pm                  |   2 +
 installer/data/mysql/atomicupdate/overdrive.sql |   1 +
 installer/data/mysql/kohastructure.sql          |   1 +
 opac/external/overdrive/auth.pl                 |  56 +++++++
 t/Koha_ExternalContent_OverDrive.t              |  38 +++++
 7 files changed, 392 insertions(+)
 create mode 100644 Koha/ExternalContent.pm
 create mode 100644 Koha/ExternalContent/OverDrive.pm
 create mode 100644 installer/data/mysql/atomicupdate/overdrive.sql
 create mode 100755 opac/external/overdrive/auth.pl
 create mode 100755 t/Koha_ExternalContent_OverDrive.t

diff --git a/Koha/ExternalContent.pm b/Koha/ExternalContent.pm
new file mode 100644
index 0000000..e459188
--- /dev/null
+++ b/Koha/ExternalContent.pm
@@ -0,0 +1,88 @@
+# Copyright 2014 Catalyst
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+package Koha::ExternalContent;
+
+use Modern::Perl;
+use Carp;
+use base qw(Class::Accessor);
+
+use Koha;
+use Koha::Patrons;
+use C4::Auth;
+
+__PACKAGE__->mk_accessors(qw(client koha_session_id koha_patron));
+
+=head1 NAME
+
+Koha::ExternalContent
+
+=head1 SYNOPSIS
+
+ use Koha::ExternalContent;
+ my $externalcontent = Koha::ExternalContent->new();
+
+=head1 METHODS
+
+=cut
+
+sub agent_string {
+    return 'Koha/'.Koha::version();
+}
+
+sub new {
+    my $class     = shift;
+    my $params    = shift || {};
+    return bless $params, $class;
+}
+
+sub _koha_session {
+    my $self = shift;
+    my $session_id = $self->koha_session_id or return;
+    return C4::Auth::get_session($session_id);
+}
+
+sub get_from_koha_session {
+    my $self = shift;
+    my $key = shift or croak "No key";
+    my $session = $self->_koha_session or return;
+    return $session->param($key);
+}
+
+sub set_in_koha_session {
+    my $self = shift;
+    my $key = shift or croak "No key";
+    my $value = shift;
+    my $session = $self->_koha_session or croak "No Koha session";
+    return $session->param($key, $value);
+}
+
+sub koha_patron {
+    my $self = shift;
+
+    if (my $patron = $self->_koha_patron_accessor) {
+        return $patron;
+    }
+
+    my $id = $self->get_from_koha_session('number')
+      or die "No patron number in session";
+    my $patron = Koha::Patrons->find($id)
+      or die "Invalid patron number in session";
+    return $self->_koha_patron_accessor($patron);
+}
+
+1;
diff --git a/Koha/ExternalContent/OverDrive.pm b/Koha/ExternalContent/OverDrive.pm
new file mode 100644
index 0000000..00f4eaf
--- /dev/null
+++ b/Koha/ExternalContent/OverDrive.pm
@@ -0,0 +1,206 @@
+# Copyright 2014 Catalyst
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+package Koha::ExternalContent::OverDrive;
+
+use Modern::Perl;
+use Carp;
+
+use base qw(Koha::ExternalContent);
+use WebService::ILS::OverDrive::Patron;
+use C4::Context;
+use Koha::Logger;
+
+use constant logger => Koha::Logger->get();
+
+=head1 NAME
+
+Koha::ExternalContent::OverDrive
+
+=head1 SYNOPSIS
+
+    use Koha::ExternalContent::OverDrive;
+    my $od_client = Koha::ExternalContent::OverDrive->new();
+    my $od_auth_url = $od_client->auth_url();
+
+=head1 DESCRIPTION
+
+A (very) thin wrapper around C<WebService::ILS::OverDrive::Patron>
+
+Takes "OverDrive*" Koha preferences
+
+=cut
+
+sub new {
+    my $class  = shift;
+    my $params = shift || {};
+    $params->{koha_session_id} or croak "No koha_session_id";
+
+    my $self = $class->SUPER::new($params);
+    unless ($params->{client}) {
+        my $client_key     = C4::Context->preference('OverDriveClientKey')
+          or croak("OverDriveClientKey pref not set");
+        my $client_secret  = C4::Context->preference('OverDriveClientSecret')
+          or croak("OverDriveClientSecret pref not set");
+        my $library_id     = C4::Context->preference('OverDriveLibraryID')
+          or croak("OverDriveLibraryID pref not set");
+        my ($token, $token_type) = $self->get_token_from_koha_session();
+        $self->client( WebService::ILS::OverDrive::Patron->new(
+            client_id         => $client_key,
+            client_secret     => $client_secret,
+            library_id        => $library_id,
+            access_token      => $token,
+            access_token_type => $token_type,
+            user_agent_params => { agent => $class->agent_string }
+        ) );
+    }
+    return $self;
+}
+
+=head1 METHODS
+
+L<WebService::ILS::OverDrive::Patron> methods used without mods:
+
+=over 4
+
+=item C<error_message()>
+
+=back
+
+C<WebService::ILS::OverDrive::Patron> methods with slightly moded interfaces:
+
+=head2 auth_url($base_url)
+
+=head2 auth_by_code($code, $base_url)
+
+=cut
+
+use constant AUTH_RETURN_HANDLER => "/cgi-bin/koha/external/overdrive/auth.pl";
+sub _return_url {
+    my $self = shift;
+    my $page_url = shift or croak "Page url not provided";
+
+    my ($base_url, $page) = ($page_url =~ m!^(https?://[^/]+)(.*)!);
+    my $return_url = $base_url.AUTH_RETURN_HANDLER;
+
+    return wantarray ? ($return_url, $page) : $return_url;
+}
+
+use constant RETURN_PAGE_SESSION_KEY => "overdrive.return_page";
+sub get_return_page_from_koha_session {
+    my $self = shift;
+    my $return_page = $self->get_from_koha_session(RETURN_PAGE_SESSION_KEY) || "";
+    $self->logger->debug("get_return_page_from_koha_session: $return_page");
+    return $return_page;
+}
+sub set_return_page_in_koha_session {
+    my $self = shift;
+    my $return_page = shift || "";
+    $self->logger->debug("set_return_page_in_koha_session: $return_page");
+    return $self->set_in_koha_session( RETURN_PAGE_SESSION_KEY, $return_page );
+}
+
+use constant ACCESS_TOKEN_SESSION_KEY => "overdrive.access_token";
+my $ACCESS_TOKEN_DELIMITER = ":";
+sub get_token_from_koha_session {
+    my $self = shift;
+    my ($token, $token_type)
+      = split $ACCESS_TOKEN_DELIMITER, $self->get_from_koha_session(ACCESS_TOKEN_SESSION_KEY) || "";
+    $self->logger->debug("get_token_from_koha_session: ".($token || "(none)"));
+    return ($token, $token_type);
+}
+sub set_token_in_koha_session {
+    my $self = shift;
+    my $token = shift || "";
+    my $token_type = shift || "";
+    $self->logger->debug("set_token_in_koha_session: $token $token_type");
+    return $self->set_in_koha_session(
+        ACCESS_TOKEN_SESSION_KEY,
+        join($ACCESS_TOKEN_DELIMITER, $token, $token_type)
+    );
+}
+
+sub is_logged_in {
+    my $self = shift;
+    my ($token, $token_type) = $self->get_token_from_koha_session();
+    $token ||= $self->auth_by_saved_token;
+    return $token;
+}
+
+sub auth_url {
+    my $self = shift;
+    my $page_url = shift or croak "Page url not provided";
+
+    my ($return_url, $page) = $self->_return_url($page_url);
+    $self->set_return_page_in_koha_session($page);
+    return $self->client->auth_url($return_url);
+}
+
+sub auth_by_code {
+    my $self = shift;
+    my $code = shift or croak "OverDrive auth code not provided";
+    my $base_url = shift or croak "App base url not provided";
+
+    my ($access_token, $access_token_type, $auth_token)
+      = $self->client->auth_by_code($code, $self->_return_url($base_url));
+    $access_token or die "Invalid OverDrive code returned";
+    $self->set_token_in_koha_session($access_token, $access_token_type);
+
+    $self->koha_patron->set({overdrive_auth_token => $auth_token})->store;
+    return $self->get_return_page_from_koha_session;
+}
+
+sub auth_by_saved_token {
+    my $self = shift;
+
+    my $koha_patron = $self->koha_patron;
+    if (my $auth_token = $koha_patron->overdrive_auth_token) {
+        my ($access_token, $access_token_type, $new_auth_token)
+          = $self->client->auth_by_token($auth_token);
+        $self->set_token_in_koha_session($access_token, $access_token_type);
+        $koha_patron->set({overdrive_auth_token => $new_auth_token})->store;
+        return $access_token;
+    }
+
+    return;
+}
+
+sub forget {
+    my $self = shift;
+
+    $self->set_token_in_koha_session("", "");
+    $self->koha_patron->update({overdrive_auth_token => undef});
+}
+
+use vars qw{$AUTOLOAD};
+sub AUTOLOAD {
+    my $self = shift;
+    (my $method = $AUTOLOAD) =~ s/.*:://;
+    my $od = $self->client;
+    local $@;
+    my $ret = eval { $od->$method(@_) };
+    if ($@) {
+        if ( $od->is_access_token_error($@) && $self->auth_by_saved_token ) {
+            return $od->$method(@_);
+        }
+        die $@;
+    }
+    return $ret;
+}
+sub DESTROY { }
+
+1;
diff --git a/Koha/Schema/Result/Borrower.pm b/Koha/Schema/Result/Borrower.pm
index 39880aa..6e434e1 100644
--- a/Koha/Schema/Result/Borrower.pm
+++ b/Koha/Schema/Result/Borrower.pm
@@ -576,6 +576,8 @@ __PACKAGE__->add_columns(
   { data_type => "integer", default_value => 1, is_nullable => 0 },
   "privacy_guarantor_checkouts",
   { data_type => "tinyint", default_value => 0, is_nullable => 0 },
+  "overdrive_auth_token",
+  { data_type => "text", is_nullable => 1 },
 );
 
 =head1 PRIMARY KEY
diff --git a/installer/data/mysql/atomicupdate/overdrive.sql b/installer/data/mysql/atomicupdate/overdrive.sql
new file mode 100644
index 0000000..2ca5e8e
--- /dev/null
+++ b/installer/data/mysql/atomicupdate/overdrive.sql
@@ -0,0 +1 @@
+ALTER TABLE borrowers ADD overdrive_auth_token text default NULL AFTER privacy_guarantor_checkouts;
diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql
index 63fefe7..b3d0d29 100644
--- a/installer/data/mysql/kohastructure.sql
+++ b/installer/data/mysql/kohastructure.sql
@@ -266,6 +266,7 @@ CREATE TABLE `borrowers` ( -- this table includes information about your patrons
   `sms_provider_id` int(11) DEFAULT NULL, -- the provider of the mobile phone number defined in smsalertnumber
   `privacy` integer(11) DEFAULT '1' NOT NULL, -- patron/borrower's privacy settings related to their reading history
   `privacy_guarantor_checkouts` tinyint(1) NOT NULL DEFAULT '0', -- controls if relatives can see this patron's checkouts
+  overdrive_auth_token text default NULL, -- persist OverDrive auth token
   UNIQUE KEY `cardnumber` (`cardnumber`),
   PRIMARY KEY `borrowernumber` (`borrowernumber`),
   KEY `categorycode` (`categorycode`),
diff --git a/opac/external/overdrive/auth.pl b/opac/external/overdrive/auth.pl
new file mode 100755
index 0000000..6ae7d17
--- /dev/null
+++ b/opac/external/overdrive/auth.pl
@@ -0,0 +1,56 @@
+#!/usr/bin/perl
+
+# script to log clicks on links to external urls
+
+# Copyright 2015 Catalyst IT
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
+
+use Modern::Perl;
+use CGI qw ( -utf8 );
+use URI;
+use URI::Escape;
+use C4::Auth qw(checkauth);
+use Koha::Logger;
+use Koha::ExternalContent::OverDrive;
+
+my $logger = Koha::Logger->get({ interface => 'opac' });
+my $cgi = new CGI;
+
+my ( $user, $cookie, $sessionID, $flags ) = checkauth( $cgi, 1, {}, 'opac' );
+my ($redirect_page, $error);
+if ($user && $sessionID) {
+    my $od = Koha::ExternalContent::OverDrive->new({ koha_session_id => $sessionID });
+    if ( my $auth_code = $cgi->param('code') ) {
+        my $base_url = $cgi->url(-base => 1);
+        local $@;
+        $redirect_page = eval { $od->auth_by_code($auth_code, $base_url) };
+        if ($@) {
+            $logger->error($@);
+            $error = $od->error_message($@);
+        }
+    }
+    else {
+        $error = "Missing OverDrive auth code";
+    }
+    $redirect_page ||= $od->get_return_page_from_koha_session;
+}
+else {
+    $error = "User not logged in";
+}
+$redirect_page ||= "/cgi-bin/koha/opac-user.pl";
+my $uri = URI->new($redirect_page);
+$uri->query_form( $uri->query_form, overdrive_tab => 1, overdrive_error => uri_escape($error || "") );
+print $cgi->redirect($redirect_page);
diff --git a/t/Koha_ExternalContent_OverDrive.t b/t/Koha_ExternalContent_OverDrive.t
new file mode 100755
index 0000000..bc6b133
--- /dev/null
+++ b/t/Koha_ExternalContent_OverDrive.t
@@ -0,0 +1,38 @@
+use Modern::Perl;
+
+use t::lib::Mocks;
+use Test::More tests => 5;                      # last test to print
+use C4::Context;
+
+my $context = C4::Context->new();
+local $@;
+eval { require WebService::ILS::OverDrive::Patron; }
+  or diag($@);
+SKIP: {
+    skip "cannot filnd WebService::ILS::OverDrive::Patron", 5 if $@;
+
+    use_ok('Koha::ExternalContent::OverDrive');
+
+    t::lib::Mocks::mock_preference('OverDriveClientKey', 'DUMMY');
+    t::lib::Mocks::mock_preference('OverDriveClientSecret', 'DUMMY');
+    t::lib::Mocks::mock_preference('OverDriveLibraryID', 'DUMMY');
+
+    my $client = Koha::ExternalContent::OverDrive->new();
+
+    my $user_agent_string = $client->user_agent->agent();
+    ok ($user_agent_string =~ m/^Koha/, 'User Agent string is set')
+      or diag("User Agent string: $user_agent_string");
+
+    my $base_url = "http://mykoha.org";
+    ok ($client->auth_url($base_url), 'auth_url()');
+    local $@;
+    eval { $client->auth_by_code("blah", $base_url) };
+    ok($@ =~ m/Invalid Access Token/i, "auth_by_code()")
+      or diag("Died: ".($@ || "nooooot"));
+    SKIP: {
+        skip "No exception", 1 unless $@;
+        my $error_message = $client->error_message($@);
+        ok($error_message =~ m/Invalid Access Token/i, "error_message()")
+          or diag("Original:\n$@\nTurned into:\n$error_message");
+    }
+}
-- 
1.9.1


More information about the Koha-patches mailing list