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 (+212 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
    use Koha::ExternalContent::OverDrive;
37
    my $od_client = Koha::ExternalContent::OverDrive->new();
38
    my $od_auth_url = $od_client->auth_url();
39
40
=head1 DESCRIPTION
41
42
A (very) thin wrapper around C<WebService::ILS::OverDrive::Patron>
43
44
Takes "OverDrive*" Koha preferences
45
46
=cut
47
48
sub new {
49
    my $class  = shift;
50
    my $params = shift || {};
51
    $params->{koha_session_id} or croak "No koha_session_id";
52
53
    my $self = $class->SUPER::new($params);
54
    unless ($params->{client}) {
55
        my $client_key     = C4::Context->preference('OverDriveClientKey')
56
          or croak("OverDriveClientKey pref not set");
57
        my $client_secret  = C4::Context->preference('OverDriveClientSecret')
58
          or croak("OverDriveClientSecret pref not set");
59
        my $library_id     = C4::Context->preference('OverDriveLibraryID')
60
          or croak("OverDriveLibraryID pref not set");
61
        my ($token, $token_type) = $self->get_token_from_koha_session();
62
        $self->client( WebService::ILS::OverDrive::Patron->new(
63
            client_id         => $client_key,
64
            client_secret     => $client_secret,
65
            library_id        => $library_id,
66
            access_token      => $token,
67
            access_token_type => $token_type,
68
            user_agent_params => { agent => $class->agent_string }
69
        ) );
70
    }
71
    return $self;
72
}
73
74
=head1 METHODS
75
76
L<WebService::ILS::OverDrive::Patron> methods used without mods:
77
78
=over 4
79
80
=item C<error_message()>
81
82
=back
83
84
C<WebService::ILS::OverDrive::Patron> methods with slightly moded interfaces:
85
86
=head2 auth_url($base_url)
87
88
=head2 auth_by_code($code, $base_url)
89
90
=cut
91
92
use constant AUTH_RETURN_HANDLER => "/cgi-bin/koha/external/overdrive/auth.pl";
93
sub _return_url {
94
    my $self = shift;
95
    my $page_url = shift or croak "Page url not provided";
96
97
    my ($base_url, $page) = ($page_url =~ m!^(https?://[^/]+)(.*)!);
98
    my $return_url = $base_url.AUTH_RETURN_HANDLER;
99
100
    return wantarray ? ($return_url, $page) : $return_url;
101
}
102
103
use constant RETURN_PAGE_SESSION_KEY => "overdrive.return_page";
104
sub get_return_page_from_koha_session {
105
    my $self = shift;
106
    my $return_page = $self->get_from_koha_session(RETURN_PAGE_SESSION_KEY) || "";
107
    $self->logger->debug("get_return_page_from_koha_session: $return_page");
108
    return $return_page;
109
}
110
sub set_return_page_in_koha_session {
111
    my $self = shift;
112
    my $return_page = shift || "";
113
    $self->logger->debug("set_return_page_in_koha_session: $return_page");
114
    return $self->set_in_koha_session( RETURN_PAGE_SESSION_KEY, $return_page );
115
}
116
117
use constant ACCESS_TOKEN_SESSION_KEY => "overdrive.access_token";
118
my $ACCESS_TOKEN_DELIMITER = ":";
119
sub get_token_from_koha_session {
120
    my $self = shift;
121
    my ($token, $token_type)
122
      = split $ACCESS_TOKEN_DELIMITER, $self->get_from_koha_session(ACCESS_TOKEN_SESSION_KEY) || "";
123
    $self->logger->debug("get_token_from_koha_session: ".($token || "(none)"));
124
    return ($token, $token_type);
125
}
126
sub set_token_in_koha_session {
127
    my $self = shift;
128
    my $token = shift || "";
129
    my $token_type = shift || "";
130
    $self->logger->debug("set_token_in_koha_session: $token $token_type");
131
    return $self->set_in_koha_session(
132
        ACCESS_TOKEN_SESSION_KEY,
133
        join($ACCESS_TOKEN_DELIMITER, $token, $token_type)
134
    );
135
}
136
137
sub is_logged_in {
138
    my $self = shift;
139
    my ($token, $token_type) = $self->get_token_from_koha_session();
140
    $token ||= $self->auth_by_saved_token;
141
    return $token;
142
}
143
144
sub auth_url {
145
    my $self = shift;
146
    my $page_url = shift or croak "Page url not provided";
147
148
    my ($return_url, $page) = $self->_return_url($page_url);
149
    $self->set_return_page_in_koha_session($page);
150
    return $self->client->auth_url($return_url);
151
}
152
153
sub auth_by_code {
154
    my $self = shift;
155
    my $code = shift or croak "OverDrive auth code not provided";
156
    my $base_url = shift or croak "App base url not provided";
157
158
    my ($access_token, $access_token_type, $auth_token)
159
      = $self->client->auth_by_code($code, $self->_return_url($base_url));
160
    $access_token or die "Invalid OverDrive code returned";
161
    $self->set_token_in_koha_session($access_token, $access_token_type);
162
163
    $self->koha_patron->set({overdrive_auth_token => $auth_token})->store;
164
    return $self->get_return_page_from_koha_session;
165
}
166
167
sub auth_by_saved_token {
168
    my $self = shift;
169
170
    my $koha_patron = $self->koha_patron;
171
    if (my $auth_token = $koha_patron->overdrive_auth_token) {
172
        my ($access_token, $access_token_type, $new_auth_token)
173
          = $self->client->auth_by_token($auth_token);
174
        $self->set_token_in_koha_session($access_token, $access_token_type);
175
        $koha_patron->set({overdrive_auth_token => $new_auth_token})->store;
176
        return $access_token;
177
    }
178
179
    return;
180
}
181
182
sub forget {
183
    my $self = shift;
184
185
    $self->set_token_in_koha_session("", "");
186
    $self->koha_patron->set({overdrive_auth_token => undef})->store;
187
}
188
189
use vars qw{$AUTOLOAD};
190
sub AUTOLOAD {
191
    my $self = shift;
192
    (my $method = $AUTOLOAD) =~ s/.*:://;
193
    my $od = $self->client;
194
    local $@;
195
    my $ret = eval { $od->$method(@_) };
196
    if ($@) {
197
        if ( $od->is_access_token_error($@) && $self->auth_by_saved_token ) {
198
            return $od->$method(@_);
199
        }
200
        die $@;
201
    }
202
    return $ret;
203
}
204
sub DESTROY { }
205
206
=head1 AUTHOR
207
208
CatalystIT
209
210
=cut
211
212
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