View | Details | Raw Unified | Return to bug 16034
Collapse All | Expand All

(-)a/Koha/ExternalContent.pm (+101 lines)
Line 0 Link Here
1
# Copyright 2014 Catalyst
2
#
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it under the
6
# terms of the GNU General Public License as published by the Free Software
7
# Foundation; either version 3 of the License, or (at your option) any later
8
# version.
9
#
10
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License along
15
# with Koha; if not, write to the Free Software Foundation, Inc.,
16
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18
package Koha::ExternalContent;
19
20
use Modern::Perl;
21
use Carp;
22
use base qw(Class::Accessor);
23
24
use Koha;
25
use Koha::Patrons;
26
use C4::Auth;
27
28
__PACKAGE__->mk_accessors(qw(client koha_session_id koha_patron));
29
30
=head1 NAME
31
32
Koha::ExternalContent
33
34
=head1 SYNOPSIS
35
36
 use Koha::ExternalContent;
37
 my $externalcontent = Koha::ExternalContent->new();
38
39
=head1 DESCRIPTION
40
41
Base class for interfacing with external content providers.
42
43
Subclasses provide clients for particular systems. This class provides
44
common methods for getting Koha patron.
45
46
=head1 METHODS
47
48
=cut
49
50
sub agent_string {
51
    return 'Koha/'.Koha::version();
52
}
53
54
sub new {
55
    my $class     = shift;
56
    my $params    = shift || {};
57
    return bless $params, $class;
58
}
59
60
sub _koha_session {
61
    my $self = shift;
62
    my $session_id = $self->koha_session_id or return;
63
    return C4::Auth::get_session($session_id);
64
}
65
66
sub get_from_koha_session {
67
    my $self = shift;
68
    my $key = shift or croak "No key";
69
    my $session = $self->_koha_session or return;
70
    return $session->param($key);
71
}
72
73
sub set_in_koha_session {
74
    my $self = shift;
75
    my $key = shift or croak "No key";
76
    my $value = shift;
77
    my $session = $self->_koha_session or croak "No Koha session";
78
    return $session->param($key, $value);
79
}
80
81
sub koha_patron {
82
    my $self = shift;
83
84
    if (my $patron = $self->_koha_patron_accessor) {
85
        return $patron;
86
    }
87
88
    my $id = $self->get_from_koha_session('number')
89
      or die "No patron number in session";
90
    my $patron = Koha::Patrons->find($id)
91
      or die "Invalid patron number in session";
92
    return $self->_koha_patron_accessor($patron);
93
}
94
95
=head1 AUTHOR
96
97
CatalystIT
98
99
=cut
100
101
1;
(-)a/Koha/ExternalContent/OverDrive.pm (+253 lines)
Line 0 Link Here
1
# Copyright 2014 Catalyst
2
#
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it under the
6
# terms of the GNU General Public License as published by the Free Software
7
# Foundation; either version 3 of the License, or (at your option) any later
8
# version.
9
#
10
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License along
15
# with Koha; if not, write to the Free Software Foundation, Inc.,
16
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18
package Koha::ExternalContent::OverDrive;
19
20
use Modern::Perl;
21
use Carp;
22
23
use base qw(Koha::ExternalContent);
24
use WebService::ILS::OverDrive::Patron;
25
use C4::Context;
26
use Koha::Logger;
27
28
use constant logger => Koha::Logger->get();
29
30
=head1 NAME
31
32
Koha::ExternalContent::OverDrive
33
34
=head1 SYNOPSIS
35
36
    Register return url with OverDrive:
37
      base app url + /cgi-bin/koha/external/overdrive/auth.pl
38
39
    use Koha::ExternalContent::OverDrive;
40
    my $od_client = Koha::ExternalContent::OverDrive->new();
41
    my $od_auth_url = $od_client->auth_url($return_page_url);
42
43
=head1 DESCRIPTION
44
45
A (very) thin wrapper around C<WebService::ILS::OverDrive::Patron>
46
47
Takes "OverDrive*" Koha preferences
48
49
=cut
50
51
sub new {
52
    my $class  = shift;
53
    my $params = shift || {};
54
    $params->{koha_session_id} or croak "No koha_session_id";
55
56
    my $self = $class->SUPER::new($params);
57
    unless ($params->{client}) {
58
        my $client_key     = C4::Context->preference('OverDriveClientKey')
59
          or croak("OverDriveClientKey pref not set");
60
        my $client_secret  = C4::Context->preference('OverDriveClientSecret')
61
          or croak("OverDriveClientSecret pref not set");
62
        my $library_id     = C4::Context->preference('OverDriveLibraryID')
63
          or croak("OverDriveLibraryID pref not set");
64
        my ($token, $token_type) = $self->get_token_from_koha_session();
65
        $self->client( WebService::ILS::OverDrive::Patron->new(
66
            client_id         => $client_key,
67
            client_secret     => $client_secret,
68
            library_id        => $library_id,
69
            access_token      => $token,
70
            access_token_type => $token_type,
71
            user_agent_params => { agent => $class->agent_string }
72
        ) );
73
    }
74
    return $self;
75
}
76
77
=head1 L<WebService::ILS::OverDrive::Patron> METHODS
78
79
Methods used without mods:
80
81
=over 4
82
83
=item C<error_message()>
84
85
=item C<patron()>
86
87
=item C<checkouts()>
88
89
=item C<holds()>
90
91
=item C<checkout($id, $format)>
92
93
=item C<checkout_download_url($id)>
94
95
=item C<return($id)>
96
97
=item C<place_hold($id)>
98
99
=item C<remove_hold($id)>
100
101
=back
102
103
Methods with slightly moded interfaces:
104
105
=head2 auth_url($page_url)
106
107
  Input: url of the page from which OverDrive authentication was requested
108
109
  Returns: Post OverDrive auth return handler url (see SYNOPSIS)
110
111
=cut
112
113
sub auth_url {
114
    my $self = shift;
115
    my $page_url = shift or croak "Page url not provided";
116
117
    my ($return_url, $page) = $self->_return_url($page_url);
118
    $self->set_return_page_in_koha_session($page);
119
    return $self->client->auth_url($return_url);
120
}
121
122
=head2 auth_by_code($code, $base_url)
123
124
  To be called in external/overdrive/auth.pl upon return from OverDrive auth
125
126
=cut
127
128
sub auth_by_code {
129
    my $self = shift;
130
    my $code = shift or croak "OverDrive auth code not provided";
131
    my $base_url = shift or croak "App base url not provided";
132
133
    my ($access_token, $access_token_type, $auth_token)
134
      = $self->client->auth_by_code($code, $self->_return_url($base_url));
135
    $access_token or die "Invalid OverDrive code returned";
136
    $self->set_token_in_koha_session($access_token, $access_token_type);
137
138
    $self->koha_patron->set({overdrive_auth_token => $auth_token})->store;
139
    return $self->get_return_page_from_koha_session;
140
}
141
142
use constant AUTH_RETURN_HANDLER => "/cgi-bin/koha/external/overdrive/auth.pl";
143
sub _return_url {
144
    my $self = shift;
145
    my $page_url = shift or croak "Page url not provided";
146
147
    my ($base_url, $page) = ($page_url =~ m!^(https?://[^/]+)(.*)!);
148
    my $return_url = $base_url.AUTH_RETURN_HANDLER;
149
150
    return wantarray ? ($return_url, $page) : $return_url;
151
}
152
153
use constant RETURN_PAGE_SESSION_KEY => "overdrive.return_page";
154
sub get_return_page_from_koha_session {
155
    my $self = shift;
156
    my $return_page = $self->get_from_koha_session(RETURN_PAGE_SESSION_KEY) || "";
157
    $self->logger->debug("get_return_page_from_koha_session: $return_page");
158
    return $return_page;
159
}
160
sub set_return_page_in_koha_session {
161
    my $self = shift;
162
    my $return_page = shift || "";
163
    $self->logger->debug("set_return_page_in_koha_session: $return_page");
164
    return $self->set_in_koha_session( RETURN_PAGE_SESSION_KEY, $return_page );
165
}
166
167
use constant ACCESS_TOKEN_SESSION_KEY => "overdrive.access_token";
168
my $ACCESS_TOKEN_DELIMITER = ":";
169
sub get_token_from_koha_session {
170
    my $self = shift;
171
    my ($token, $token_type)
172
      = split $ACCESS_TOKEN_DELIMITER, $self->get_from_koha_session(ACCESS_TOKEN_SESSION_KEY) || "";
173
    $self->logger->debug("get_token_from_koha_session: ".($token || "(none)"));
174
    return ($token, $token_type);
175
}
176
sub set_token_in_koha_session {
177
    my $self = shift;
178
    my $token = shift || "";
179
    my $token_type = shift || "";
180
    $self->logger->debug("set_token_in_koha_session: $token $token_type");
181
    return $self->set_in_koha_session(
182
        ACCESS_TOKEN_SESSION_KEY,
183
        join($ACCESS_TOKEN_DELIMITER, $token, $token_type)
184
    );
185
}
186
187
=head1 OTHER METHODS
188
189
=head2 is_logged_in()
190
191
  Returns boolean
192
193
=cut
194
195
sub is_logged_in {
196
    my $self = shift;
197
    my ($token, $token_type) = $self->get_token_from_koha_session();
198
    $token ||= $self->auth_by_saved_token;
199
    return $token;
200
}
201
202
sub auth_by_saved_token {
203
    my $self = shift;
204
205
    my $koha_patron = $self->koha_patron;
206
    if (my $auth_token = $koha_patron->overdrive_auth_token) {
207
        my ($access_token, $access_token_type, $new_auth_token)
208
          = $self->client->auth_by_token($auth_token);
209
        $self->set_token_in_koha_session($access_token, $access_token_type);
210
        $koha_patron->set({overdrive_auth_token => $new_auth_token})->store;
211
        return $access_token;
212
    }
213
214
    return;
215
}
216
217
=head2 forget()
218
219
  Removes stored OverDrive token
220
221
=cut
222
223
sub forget {
224
    my $self = shift;
225
226
    $self->set_token_in_koha_session("", "");
227
    $self->koha_patron->set({overdrive_auth_token => undef})->store;
228
}
229
230
use vars qw{$AUTOLOAD};
231
sub AUTOLOAD {
232
    my $self = shift;
233
    (my $method = $AUTOLOAD) =~ s/.*:://;
234
    my $od = $self->client;
235
    local $@;
236
    my $ret = eval { $od->$method(@_) };
237
    if ($@) {
238
        if ( $od->is_access_token_error($@) && $self->auth_by_saved_token ) {
239
            return $od->$method(@_);
240
        }
241
        die $@;
242
    }
243
    return $ret;
244
}
245
sub DESTROY { }
246
247
=head1 AUTHOR
248
249
CatalystIT
250
251
=cut
252
253
1;
(-)a/Koha/Schema/Result/Borrower.pm (+2 lines)
Lines 616-621 __PACKAGE__->add_columns( Link Here
616
    datetime_undef_if_invalid => 1,
616
    datetime_undef_if_invalid => 1,
617
    is_nullable => 1,
617
    is_nullable => 1,
618
  },
618
  },
619
  "overdrive_auth_token",
620
  { data_type => "text", is_nullable => 1 },
619
);
621
);
620
622
621
=head1 PRIMARY KEY
623
=head1 PRIMARY KEY
(-)a/installer/data/mysql/atomicupdate/overdrive.sql (+1 lines)
Line 0 Link Here
1
ALTER TABLE borrowers ADD overdrive_auth_token text default NULL AFTER lastseen;
(-)a/installer/data/mysql/kohastructure.sql (+1 lines)
Lines 1655-1660 CREATE TABLE `borrowers` ( -- this table includes information about your patrons Link Here
1655
  `checkprevcheckout` varchar(7) NOT NULL default 'inherit', -- produce a warning for this patron if this item has previously been checked out to this patron if 'yes', not if 'no', defer to category setting if 'inherit'.
1655
  `checkprevcheckout` varchar(7) NOT NULL default 'inherit', -- produce a warning for this patron if this item has previously been checked out to this patron if 'yes', not if 'no', defer to category setting if 'inherit'.
1656
  `updated_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- time of last change could be useful for synchronization with external systems (among others)
1656
  `updated_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- time of last change could be useful for synchronization with external systems (among others)
1657
  `lastseen` datetime default NULL, -- last time a patron has been seed (connected at the OPAC or staff interface)
1657
  `lastseen` datetime default NULL, -- last time a patron has been seed (connected at the OPAC or staff interface)
1658
  overdrive_auth_token text default NULL, -- persist OverDrive auth token
1658
  UNIQUE KEY `cardnumber` (`cardnumber`),
1659
  UNIQUE KEY `cardnumber` (`cardnumber`),
1659
  PRIMARY KEY `borrowernumber` (`borrowernumber`),
1660
  PRIMARY KEY `borrowernumber` (`borrowernumber`),
1660
  KEY `categorycode` (`categorycode`),
1661
  KEY `categorycode` (`categorycode`),
(-)a/opac/external/overdrive/auth.pl (+56 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# script to handle redirect back from OverDrive auth endpoint
4
5
# Copyright 2015 Catalyst IT
6
# This file is part of Koha.
7
#
8
# Koha is free software; you can redistribute it and/or modify it
9
# under the terms of the GNU General Public License as published by
10
# the Free Software Foundation; either version 3 of the License, or
11
# (at your option) any later version.
12
#
13
# Koha is distributed in the hope that it will be useful, but
14
# WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
# GNU General Public License for more details.
17
#
18
# You should have received a copy of the GNU General Public License
19
# along with Koha; if not, see <http://www.gnu.org/licenses>.
20
21
use Modern::Perl;
22
use CGI qw ( -utf8 );
23
use URI;
24
use URI::Escape;
25
use C4::Auth qw(checkauth);
26
use Koha::Logger;
27
use Koha::ExternalContent::OverDrive;
28
29
my $logger = Koha::Logger->get({ interface => 'opac' });
30
my $cgi = new CGI;
31
32
my ( $user, $cookie, $sessionID, $flags ) = checkauth( $cgi, 1, {}, 'opac' );
33
my ($redirect_page, $error);
34
if ($user && $sessionID) {
35
    my $od = Koha::ExternalContent::OverDrive->new({ koha_session_id => $sessionID });
36
    if ( my $auth_code = $cgi->param('code') ) {
37
        my $base_url = $cgi->url(-base => 1);
38
        local $@;
39
        $redirect_page = eval { $od->auth_by_code($auth_code, $base_url) };
40
        if ($@) {
41
            $logger->error($@);
42
            $error = $od->error_message($@);
43
        }
44
    }
45
    else {
46
        $error = "Missing OverDrive auth code";
47
    }
48
    $redirect_page ||= $od->get_return_page_from_koha_session;
49
}
50
else {
51
    $error = "User not logged in";
52
}
53
$redirect_page ||= "/cgi-bin/koha/opac-user.pl";
54
my $uri = URI->new($redirect_page);
55
$uri->query_form( $uri->query_form, overdrive_tab => 1, overdrive_error => uri_escape($error || "") );
56
print $cgi->redirect($redirect_page);
(-)a/t/Koha_ExternalContent_OverDrive.t (-1 / +35 lines)
Line 0 Link Here
0
- 
1
use Modern::Perl;
2
3
use t::lib::Mocks;
4
use Test::More tests => 5;                      # last test to print
5
6
local $@;
7
eval { require WebService::ILS::OverDrive::Patron; }
8
  or diag($@);
9
SKIP: {
10
    skip "cannot filnd WebService::ILS::OverDrive::Patron", 5 if $@;
11
12
    use_ok('Koha::ExternalContent::OverDrive');
13
14
    t::lib::Mocks::mock_preference('OverDriveClientKey', 'DUMMY');
15
    t::lib::Mocks::mock_preference('OverDriveClientSecret', 'DUMMY');
16
    t::lib::Mocks::mock_preference('OverDriveLibraryID', 'DUMMY');
17
18
    my $client = Koha::ExternalContent::OverDrive->new({koha_session_id => 'DUMMY'});
19
20
    my $user_agent_string = $client->user_agent->agent();
21
    ok ($user_agent_string =~ m/^Koha/, 'User Agent string is set')
22
      or diag("User Agent string: $user_agent_string");
23
24
    my $base_url = "http://mykoha.org";
25
    ok ($client->auth_url($base_url), 'auth_url()');
26
    local $@;
27
    eval { $client->auth_by_code("blah", $base_url) };
28
    ok($@, "auth_by_code() dies with bogus credentials");
29
    SKIP: {
30
        skip "No exception", 1 unless $@;
31
        my $error_message = $client->error_message($@);
32
        ok($error_message =~ m/Authorization Failed/i, "error_message()")
33
          or diag("Original:\n$@\nTurned into:\n$error_message");
34
    }
35
}

Return to bug 16034