Bug 28959: Add virtualshelves.public as a boolean
[koha.git] / Koha / ExternalContent / OverDrive.pm
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
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # Koha is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18 package Koha::ExternalContent::OverDrive;
19
20 use Modern::Perl;
21 use Carp qw( croak );
22
23 use base qw(Koha::ExternalContent);
24 use WebService::ILS::OverDrive::Patron;
25 use C4::Context;
26
27 =head1 NAME
28
29 Koha::ExternalContent::OverDrive
30
31 =head1 SYNOPSIS
32
33     Register return url with OverDrive:
34       base app url + /cgi-bin/koha/external/overdrive/auth.pl
35
36     use Koha::ExternalContent::OverDrive;
37     my $od_client = Koha::ExternalContent::OverDrive->new();
38     my $od_auth_url = $od_client->auth_url($return_page_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
72     return $self;
73 }
74
75 =head1 L<WebService::ILS::OverDrive::Patron> METHODS
76
77 Methods used without mods:
78
79 =over 4
80
81 =item C<error_message()>
82
83 =item C<patron()>
84
85 =item C<checkouts()>
86
87 =item C<holds()>
88
89 =item C<checkout($id, $format)>
90
91 =item C<checkout_download_url($id)>
92
93 =item C<return($id)>
94
95 =item C<place_hold($id)>
96
97 =item C<remove_hold($id)>
98
99 =back
100
101 Methods with slightly moded interfaces:
102
103 =head2 auth_url($page_url)
104
105   Input: url of the page from which OverDrive authentication was requested
106
107   Returns: Post OverDrive auth return handler url (see SYNOPSIS)
108
109 =cut
110
111 sub auth_url {
112     my $self = shift;
113     my $page_url = shift or croak "Page url not provided";
114
115     my ($return_url, $page) = $self->_return_url($page_url);
116     $self->set_return_page_in_koha_session($page);
117     return $self->client->auth_url($return_url);
118 }
119
120 =head2 auth_by_code($code, $base_url)
121
122   To be called in external/overdrive/auth.pl upon return from OverDrive Granted auth
123
124 =cut
125
126 sub auth_by_code {
127     my $self = shift;
128     my $code = shift or croak "OverDrive auth code not provided";
129     my $base_url = shift or croak "App base url not provided";
130
131     my ($access_token, $access_token_type, $auth_token)
132       = $self->client->auth_by_code($code, $self->_return_url($base_url));
133     $access_token or die "Invalid OverDrive code returned";
134     $self->set_token_in_koha_session($access_token, $access_token_type);
135
136     if (my $koha_patron = $self->koha_patron) {
137         $koha_patron->set({overdrive_auth_token => $auth_token})->store;
138     }
139     return $self->get_return_page_from_koha_session;
140 }
141
142 =head2 auth_by_userid($userid, $password, $website_id, $authorization_name)
143
144   To be called to check auth of patron using OverDrive Patron Authentication method
145   This requires a SIP connection configured with OverDrive
146
147 =cut
148
149 sub auth_by_userid {
150     my $self = shift;
151     my $userid = shift or croak "No user provided";
152     my $password = shift;
153     croak "No password provided" unless ($password || !C4::Context->preference("OverDrivePasswordRequired"));
154     my $website_id = shift or croak "OverDrive Library ID not provided";
155     my $authorization_name = shift or croak "OverDrive Authname not provided";
156
157     my ($access_token, $access_token_type, $auth_token)
158       = $self->client->auth_by_user_id($userid, $password, $website_id, $authorization_name);
159     $access_token or die "Invalid OverDrive code returned";
160     $self->set_token_in_koha_session($access_token, $access_token_type);
161
162     $self->koha_patron->set({overdrive_auth_token => $auth_token})->store;
163     return $self->get_return_page_from_koha_session;
164 }
165
166 use constant AUTH_RETURN_HANDLER => "/cgi-bin/koha/external/overdrive/auth.pl";
167 sub _return_url {
168     my $self = shift;
169     my $page_url = shift or croak "Page url not provided";
170
171     my ($base_url, $page) = ($page_url =~ m!^(https?://[^/]+)(.*)!);
172     my $return_url = $base_url.AUTH_RETURN_HANDLER;
173
174     return wantarray ? ($return_url, $page) : $return_url;
175 }
176
177 use constant RETURN_PAGE_SESSION_KEY => "overdrive.return_page";
178 sub get_return_page_from_koha_session {
179     my $self = shift;
180     my $return_page = $self->get_from_koha_session(RETURN_PAGE_SESSION_KEY) || "";
181     $self->logger->debug("get_return_page_from_koha_session: $return_page");
182     return $return_page;
183 }
184 sub set_return_page_in_koha_session {
185     my $self = shift;
186     my $return_page = shift || "";
187     $self->logger->debug("set_return_page_in_koha_session: $return_page");
188     return $self->set_in_koha_session( RETURN_PAGE_SESSION_KEY, $return_page );
189 }
190
191 use constant ACCESS_TOKEN_SESSION_KEY => "overdrive.access_token";
192 my $ACCESS_TOKEN_DELIMITER = ":";
193 sub get_token_from_koha_session {
194     my $self = shift;
195     my ($token, $token_type)
196       = split $ACCESS_TOKEN_DELIMITER, $self->get_from_koha_session(ACCESS_TOKEN_SESSION_KEY) || "";
197     $self->logger->debug("get_token_from_koha_session: ".($token || "(none)"));
198     return ($token, $token_type);
199 }
200 sub set_token_in_koha_session {
201     my $self = shift;
202     my $token = shift || "";
203     my $token_type = shift || "";
204     $self->logger->debug("set_token_in_koha_session: $token $token_type");
205     return $self->set_in_koha_session(
206         ACCESS_TOKEN_SESSION_KEY,
207         join($ACCESS_TOKEN_DELIMITER, $token, $token_type)
208     );
209 }
210
211 =head1 OTHER METHODS
212
213 =head2 is_logged_in()
214
215   Returns boolean
216
217 =cut
218
219 sub is_logged_in {
220     my $self = shift;
221     my ($token, $token_type) = $self->get_token_from_koha_session();
222     $token ||= $self->auth_by_saved_token;
223     return $token;
224 }
225
226 sub auth_by_saved_token {
227     my $self = shift;
228
229     my $koha_patron = $self->koha_patron or return;
230
231     if (my $auth_token = $koha_patron->overdrive_auth_token) {
232         my ($access_token, $access_token_type, $new_auth_token)
233           = $self->client->make_access_token_request();
234         $self->set_token_in_koha_session($access_token, $access_token_type);
235         $koha_patron->set({overdrive_auth_token => $new_auth_token})->store;
236         return $access_token;
237     }
238
239     return;
240 }
241
242 =head2 forget()
243
244   Removes stored OverDrive token
245
246 =cut
247
248 sub forget {
249     my $self = shift;
250
251     $self->set_token_in_koha_session("", "");
252     if (my $koha_patron = $self->koha_patron) {
253         $koha_patron->set({overdrive_auth_token => undef})->store;
254     }
255 }
256
257 use vars qw{$AUTOLOAD};
258 sub AUTOLOAD {
259     my $self = shift;
260     (my $method = $AUTOLOAD) =~ s/.*:://;
261     my $od = $self->client;
262     local $@;
263     my $ret = eval { $od->$method(@_) };
264     if ($@) {
265         if ( $od->is_access_token_error($@) && $self->auth_by_saved_token ) {
266             return $od->$method(@_);
267         }
268         die $@;
269     }
270     return $ret;
271 }
272 sub DESTROY { }
273
274 =head1 AUTHOR
275
276 CatalystIT
277
278 =cut
279
280 1;