<Location /sip2-mediator>
SetHandler perl-script
- PerlHandler OpenILS::WWW::SIP2Gateway
+ PerlHandler OpenILS::WWW::SIP2Mediator
Options +ExecCGI
Require all granted
</Location>
my ($session, $message) = @_;
my $sip_account = $session->sip_account;
- my $barcode = get_field_value($message, 'AA');
- my $password = get_field_value($message, 'AD');
+ my $barcode = $SC->get_field_value($message, 'AA');
+ my $password = $SC->get_field_value($message, 'AD');
my $summary =
ref $message->{fixed_fields} ? $message->{fixed_fields}->[2] : '';
push(
@{$response->{fixed_fields}},
- count4($pdetails->{holds_count}),
- count4($pdetails->{overdue_count}),
- count4($pdetails->{out_count}),
- count4($pdetails->{fine_count}),
- count4($pdetails->{recall_count}),
- count4($pdetails->{unavail_holds_count})
+ $SC->count4($pdetails->{holds_count}),
+ $SC->count4($pdetails->{overdue_count}),
+ $SC->count4($pdetails->{out_count}),
+ $SC->count4($pdetails->{fine_count}),
+ $SC->count4($pdetails->{recall_count}),
+ $SC->count4($pdetails->{unavail_holds_count})
);
# TODO: Add
+++ /dev/null
-# ---------------------------------------------------------------
-# Copyright (C) 2020 King County Library System
-# Bill Erickson <berickxx@gmail.com>
-#
-# This program 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 2
-# of the License, or (at your option) any later version.
-
-# This program 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.
-#
-# ---------------------------------------------------------------
-# Code borrows heavily and sometimes copies directly from from
-# ../SIP* and SIPServer*
-# ---------------------------------------------------------------
-package OpenILS::WWW::SIPSession;
-use strict; use warnings;
-use OpenSRF::Utils::Cache;
-use OpenSRF::Utils::Logger q/$logger/;
-use OpenILS::Application::AppUtils;
-use OpenILS::Utils::CStoreEditor q/:funcs/;
-my $U = 'OpenILS::Application::AppUtils';
-
-# Cache instances cannot be created until opensrf is connected.
-my $_cache;
-sub cache {
- $_cache = OpenSRF::Utils::Cache->new unless $_cache;
- return $_cache;
-}
-
-sub new {
- my ($class, %args) = @_;
- my $self = bless(\%args, $class);
-}
-
-# Create a new sessesion from cached data.
-sub from_cache {
- my ($class, $seskey) = @_;
-
- my $ses = cache()->get_cache("sip2_$seskey");
-
- if ($ses) {
-
- my $session = $class->new(
- seskey => $seskey,
- sip_account => $ses->{sip_account}
- );
-
- $session->editor->authtoken($ses->{ils_authtoken});
-
- return $session if $session->set_ils_account;
-
- return undef;
-
- } else {
-
- $logger->warn("SIP2: No session found in cache for key $seskey");
- return undef;
- }
-}
-
-# The editor contains the authtoken and ILS user account (requestor).
-sub editor {
- my $self = shift;
- $self->{editor} = new_editor() unless $self->{editor};
- return $self->{editor};
-}
-
-sub seskey {
- my $self = shift;
- return $self->{seskey};
-}
-
-# SIP account
-sub sip_account {
- my $self = shift;
- return $self->{sip_account};
-}
-
-# Logs in to Evergreen and caches the auth token/login with the SIP
-# account data.
-# Returns true on success, false on failure to authenticate.
-sub set_ils_account {
- my $self = shift;
-
- # Verify previously applied authtoken is still valid.
- return 1 if $self->editor->authtoken && $self->editor->checkauth;
-
- my $seskey = $self->seskey;
-
- my $auth = $U->simplereq(
- 'open-ils.auth_internal',
- 'open-ils.auth_internal.session.create', {
- user_id => $self->sip_account->usr,
- workstation => $self->sip_account->workstation->name,
- login_type => 'staff'
- });
-
- if ($auth->{textcode} ne 'SUCCESS') {
- $logger->warn(
- "SIP2 failed to create an internal login session for ILS user: ".
- $self->sip_account->usr);
- return 0;
- }
-
- my $ses = {
- sip_account => $self->sip_account,
- ils_authtoken => $auth->{payload}->{authtoken}
- };
-
- $self->editor->authtoken($ses->{ils_authtoken});
- $self->editor->checkauth;
-
- cache()->put_cache("sip2_$seskey", $ses);
- return 1;
-}
-
-package OpenILS::WWW::SIP2Gateway;
-use strict; use warnings;
-use Apache2::Const -compile =>
- qw(OK FORBIDDEN NOT_FOUND HTTP_INTERNAL_SERVER_ERROR HTTP_BAD_REQUEST);
-use Apache2::RequestRec;
-use CGI;
-use DateTime;
-use DateTime::Format::ISO8601;
-use JSON::XS;
-use OpenSRF::System;
-use OpenILS::Utils::CStoreEditor q/:funcs/;
-use OpenSRF::Utils::Logger q/$logger/;
-use OpenILS::Application::AppUtils;
-use OpenILS::Utils::DateTime qw/:datetime/;
-use OpenILS::Const qw/:const/;
-use OpenILS::WWW::SIP2Gateway::Patron;
-use OpenILS::WWW::SIP2Gateway::Item;
-
-my $json = JSON::XS->new;
-$json->ascii(1);
-$json->allow_nonref(1);
-
-use constant SIP_DATE_FORMAT => "%Y%m%d %H%M%S";
-
-# Supported Messages (BX)
-# Currently hard-coded, since it's based on availabilty of functionality
-# in the code, but it could be moved into the database to limit access for
-# specific institutions.
-use constant INSTITUTION_SUPPORTS => [
- 'Y', # patron status request,
- 'Y', # checkout,
- 'Y', # checkin,
- 'N', # block patron,
- 'Y', # acs status,
- 'N', # request sc/acs resend,
- 'Y', # login,
- 'Y', # patron information,
- 'N', # end patron session,
- 'Y', # fee paid,
- 'Y', # item information,
- 'N', # item status update,
- 'N', # patron enable,
- 'N', # hold,
- 'Y', # renew,
- 'N', # renew all,
-];
-
-my $osrf_config;
-sub import {
- $osrf_config = shift;
-}
-
-# We need a global pcrud editor for pre-auth activities.
-# Each SIPSession will have its own post-auth pcrud editor.
-my $editor;
-my $config;
-my $init_complete = 0;
-sub init {
- return if $init_complete;
- $init_complete = 1;
-
- OpenSRF::System->bootstrap_client(config_file => $osrf_config);
- OpenILS::Utils::CStoreEditor->init;
-
- $editor = new_editor();
-
- my $settings = $editor->retrieve_all_config_sip_setting;
-
- $config = {institutions => []};
-
- # Institution specific settings.
- # In addition to the settings, this tells us what institutions we support.
- for my $set (grep {$_->institution ne '*'} @$settings) {
- my $inst = $set->institution;
- my $value = $json->decode($set->value);
- my $name = $set->name;
-
- my ($inst_conf) = grep {$_->{id} eq $inst} @{$config->{institutions}};
-
- if (!$inst_conf) {
- $inst_conf = {
- id => $inst,
- settings => {},
- supports => INSTITUTION_SUPPORTS
- };
-
- push(@{$config->{institutions}}, $inst_conf);
- }
-
- $inst_conf->{settings}->{$name} = $value;
- }
-
- # Apply values for global settings without replacing
- # institution-specific values.
- for my $set (grep {$_->institution eq '*'} @$settings) {
- my $name = $set->name;
- my $value = $json->decode($set->value);
-
- for my $inst_conf (@{$config->{institutions}}) {
- $inst_conf->{settings}->{$name} = $value
- unless exists $inst_conf->{settings}->{$name};
- }
- }
-}
-
-sub sipdate {
- my $date = shift || DateTime->now;
- return $date->strftime(SIP_DATE_FORMAT);
-}
-
-# False == 'N'
-sub sipbool {
- my $bool = shift;
- return $bool ? 'Y' : 'N';
-}
-
-# False == ' '
-sub spacebool {
- my $bool = shift;
- return $bool ? 'Y' : ' ';
-}
-
-sub count4 {
- my $value = shift;
- return ' ' unless defined $value;
- return sprintf("%04d", $value);
-}
-
-sub handler {
- my $r = shift;
- my $cgi = CGI->new;
- my ($message, $msg_code, $response);
-
- init();
-
- my $seskey = $cgi->param('session');
- my $msg_json = $cgi->param('message');
-
- if ($seskey && $msg_json) {
- eval { $message = $json->decode($msg_json) };
- if ($message) {
- $msg_code = $message->{code};
- } else {
- $logger->error("SIP2: Error parsing message JSON: $@ : $msg_json");
- }
- }
-
- return Apache2::Const::HTTP_BAD_REQUEST unless $msg_code;
-
- if ($msg_code eq '93') {
- $response = handle_login($seskey, $message);
-
- } elsif ($msg_code eq '99') {
- $response = handle_sc_status($seskey, $message);
-
- } else {
-
- # A cached session means we have successfully logged in with
- # the SIP credentials provided during a login request. All
- # message types following require authentication.
- my $session = OpenILS::WWW::SIPSession->from_cache($seskey);
-
- return Apache2::Const::FORBIDDEN unless $session;
-
- if ($msg_code eq '17') {
- $response = handle_item_info($session, $message);
- } elsif ($msg_code eq '23') {
- $response = handle_patron_status($session, $message);
- } elsif ($msg_code eq '63') {
- $response = handle_patron_info($session, $message);
- }
- }
-
- unless ($response) {
- $logger->error("SIP2: no response generated for: $msg_code");
- return Apache2::Const::NOT_FOUND;
- }
-
- $r->content_type('application/json');
- $r->print($json->encode($response));
-
- return Apache2::Const::OK;
-}
-
-
-# Returns the value of the first occurrence of the requested SIP code.
-sub get_field_value {
- my ($message, $code) = @_;
- for my $field (@{$message->{fields}}) {
- while (my ($c, $v) = each(%$field)) { # one pair per field
- return $v if $c eq $code;
- }
- }
-
- return undef;
-}
-
-# Login to Evergreen and cache the login data.
-sub handle_login {
- my ($seskey, $message) = @_;
-
- my $response = {
- code => '94',
- fixed_fields => ['0'] # default to login failed.
- };
-
- my $sip_username = get_field_value($message, 'CN');
- my $sip_password = get_field_value($message, 'CO');
- my $sip_account = $editor->search_config_sip_account([
- {sip_username => $sip_username, enabled => 't'},
- {flesh => 1, flesh_fields => {csa => ['workstation']}}
- ])->[0];
-
- if (!$sip_account) {
- $logger->warn("SIP2: No such SIP account: $sip_username");
- return $response;
- }
-
- if ($U->verify_user_password(
- $editor, $sip_account->usr, $sip_password, 'sip2')) {
-
- my $session = OpenILS::WWW::SIPSession->new(
- seskey => $seskey,
- sip_account => $sip_account
- );
- $response->{fixed_fields}->[0] = '1' if $session->set_ils_account;
-
- } else {
- $logger->info("SIP2: login failed for user=$sip_username")
- }
-
- return $response;
-}
-
-sub handle_sc_status {
- my ($seskey, $message) = @_;
-
- my $session = OpenILS::WWW::SIPSession->from_cache($seskey);
-
- my $instname;
- if ($session) {
- $instname = $session->sip_account->institution;
-
- } else {
-
- # SC Status requires login?
- return undef unless
- $config->{settings}->{allow_sc_status_before_login};
-
- # The SC Status message does not include an institution, but expects
- # one in return. Use the configuration for the first institution.
- # Maybe the SIP server itself should track which institutoin its
- # instance is configured to use? That may multiple servers could
- # run, one per institution.
- $instname = $config->{institutions}->[0]->{id};
- }
-
- my ($instconf) =
- grep {$_->{id} eq $instname} @{$config->{institutions}};
-
- if (!$instconf) {
- $logger->warn("SIP2: No config for institution '$instname'");
- $instconf = {supports => []};
- }
-
- my $response = {
- code => '98',
- fixed_fields => [
- sipbool(1), # online_status
- sipbool(1), # checkin_ok
- sipbool(1), # checkout_ok
- sipbool(1), # acs_renewal_policy
- sipbool(0), # status_update_ok
- sipbool(0), # offline_ok
- '999', # timeout_period
- '999', # retries_allowed
- sipdate(), # transaction date
- '2.00' # protocol_version
- ],
- fields => [
- {AO => $instname},
- {BX => join('', @{$instconf->{supports}})}
- ]
- }
-}
-
-sub handle_item_info {
- my ($session, $message) = @_;
-
- my $sip_account = $session->sip_account;
- my $institution = get_field_value($message, 'AO');
- my $instconf = get_inst_config($institution) || return undef;
- my $barcode = get_field_value($message, 'AB');
-
- my $idetails = OpenILS::WWW::SIP2Gateway::Item->get_item_details(
- session => $session,
- instconf => $instconf,
- barcode => $barcode
- );
-
- if (!$idetails) {
- # No matching item found, return a minimal response.
- return {
- code => '18',
- fixed_fields => [
- '01', # circ status: other/Unknown
- '01', # security marker: other/unknown
- '01', # fee type: other/unknown
- sipdate()
- ],
- fields => [{AB => $barcode, AJ => ''}]
- };
- };
-
- return {
- code => '18',
- fixed_fields => [
- $idetails->{circ_status},
- '02', # Security Marker, consistent with ../SIP*
- $idetails->{fee_type},
- sipdate()
- ],
- fields => [
- {AB => $barcode},
- {AJ => $idetails->{title}},
- {CF => $idetails->{hold_queue_length}},
- {AH => $idetails->{due_date}},
- {CM => $idetails->{hold_pickup_date}},
- {BG => $idetails->{item}->circ_lib->shortname},
- {BH => $instconf->{currency}},
- {BV => $idetails->{item}->deposit_amount},
- {CK => $idetails->{media_type}},
- {AQ => $idetails->{item}->circ_lib->shortname},
- {AP => $idetails->{item}->circ_lib->shortname},
- ]
- };
-}
-
-sub handle_patron_info {
- my ($session, $message) = @_;
- my $sip_account = $session->sip_account;
-
- my $institution = get_field_value($message, 'AO');
- my $barcode = get_field_value($message, 'AA');
- my $password = get_field_value($message, 'AD');
- my $instconf = get_inst_config($institution) || return undef;
- my $summary = $message->{fixed_fields}->[2];
-
- my $pdetails = OpenILS::WWW::SIP2Gateway::Patron->get_patron_details(
- session => $session,
- instconf => $instconf,
- barcode => $barcode,
- password => $password,
- summary_start_item => get_field_value($message, 'BP'),
- summary_end_item => get_field_value($message, 'BQ'),
- summary_list_items => patron_summary_list_items($summary)
- );
-
- my $response = patron_response_common_data(
- $session, $institution, $barcode, $password, $pdetails);
-
- $response->{code} = '64';
-
- return $response unless $pdetails;
-
- push(
- @{$response->{fixed_fields}},
- count4($pdetails->{holds_count}),
- count4($pdetails->{overdue_count}),
- count4($pdetails->{out_count}),
- count4($pdetails->{fine_count}),
- count4($pdetails->{recall_count}),
- count4($pdetails->{unavail_holds_count})
- );
-
- # TODO: Add
- # overdue items AT variable-length optional field (this field should be sent for each overdue item).
- # charged items AU variable-length optional field (this field should be sent for each charged item).
- # fine items AV variable-length optional field (this field should be sent for each fine item).
- # recall items BU variable-length optional field (this field should be sent for each recall item).
- # unavailable hold items CD variable-length optional field (this field should be sent for each unavailable hold item).
-
- my $list_items = patron_summary_list_items($summary) || '';
-
- if ($list_items eq 'hold_items') {
- for my $hold (@{$pdetails->{hold_items}}) {
- push(@{$response->{fields}}, {AS => $hold});
- }
- }
-
- return $response;
-}
-
-sub handle_patron_status {
- my ($session, $message) = @_;
- my $sip_account = $session->sip_account;
-
- my $institution = get_field_value($message, 'AO');
- my $barcode = get_field_value($message, 'AA');
- my $password = get_field_value($message, 'AD');
- my $instconf = get_inst_config($institution) || return undef;
-
- my $pdetails = OpenILS::WWW::SIP2Gateway::Patron->get_patron_details(
- session => $session,
- instconf => $instconf,
- barcode => $barcode,
- password => $password
- );
-
- my $response = patron_response_common_data(
- $session, $institution, $barcode, $password, $pdetails);
-
- $response->{code} = '24';
-
- return $response;
-}
-
-# Patron Info and Patron Status responses share mostly the same data.
-# This returns the base data which can be augmented as needed.
-# Note we don't call Patron->get_patron_details here since different
-# messages collect different amounts of data.
-sub patron_response_common_data {
- my ($session, $institution, $barcode, $password, $pdetails) = @_;
-
- if (!$pdetails) {
- # No such user. Return a stub response with all things denied.
-
- return {
- fixed_fields => [
- spacebool(1), # charge denied
- spacebool(1), # renew denied
- spacebool(1), # recall denied
- spacebool(1), # holds denied
- split('', (' ' x 10)),
- '000', # language
- sipdate()
- ],
- fields => [
- {AO => $institution},
- {AA => $barcode},
- {BL => sipbool(0)}, # valid patron
- {CQ => sipbool(0)} # valid patron password
- ]
- };
- }
-
- return {
- fixed_fields => [
- spacebool($pdetails->{charge_denied}),
- spacebool($pdetails->{renew_denied}),
- spacebool($pdetails->{recall_denied}),
- spacebool($pdetails->{holds_denied}),
- spacebool($pdetails->{patron}->card->active eq 'f'),
- spacebool(0), # too many charged
- spacebool($pdetails->{too_may_overdue}),
- spacebool(0), # too many renewals
- spacebool(0), # too many claims retruned
- spacebool(0), # too many lost
- spacebool($pdetails->{too_many_fines}),
- spacebool($pdetails->{too_many_fines}),
- spacebool(0), # recall overdue
- spacebool($pdetails->{too_many_fines}),
- '000', # language
- sipdate()
- ],
- fields => [
- {AO => $institution},
- {AA => $barcode},
- {BL => sipbool(1)}, # valid patron
- {BV => $pdetails->{balance_owed}}, # fee amount
- {CQ => sipbool($password)} # password verified if exists
- ]
- };
-}
-
-# Determines which class of data the SIP client wants detailed
-# information on in the patron info request.
-sub patron_summary_list_items {
- my $summary = shift;
-
- my $idx = index($summary, 'Y');
-
- return 'hold_items' if $idx == 0;
- return 'overdue_items' if $idx == 1;
- return 'charged_items' if $idx == 2;
- return 'fine_items' if $idx == 3;
- return 'recall_items' if $idx == 4;
- return 'unavailable_holds' if $idx == 5;
-}
-
-1;
+++ /dev/null
-# ---------------------------------------------------------------
-# Copyright (C) 2020 King County Library System
-# Bill Erickson <berickxx@gmail.com>
-#
-# This program 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 2
-# of the License, or (at your option) any later version.
-
-# This program 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.
-# ---------------------------------------------------------------
-package OpenILS::WWW::SIP2Gateway::Item;
-use strict; use warnings;
-use DateTime;
-use DateTime::Format::ISO8601;
-use OpenSRF::System;
-use OpenILS::Utils::CStoreEditor q/:funcs/;
-use OpenSRF::Utils::Logger q/$logger/;
-use OpenILS::Application::AppUtils;
-use OpenILS::Utils::DateTime qw/:datetime/;
-use OpenILS::Const qw/:const/;
-my $U = 'OpenILS::Application::AppUtils';
-
-sub get_item_details {
- my ($class, %params) = @_;
-
- my $session = $params{session};
- my $instconf = $params{instconf};
- my $barcode = $params{barcode};
-
- my $e = new_editor();
-
- my $item = $e->search_asset_copy([{
- barcode => $barcode,
- deleted => 'f'
- }, {
- flesh => 3,
- flesh_fields => {
- acp => [qw/circ_lib call_number
- status stat_cat_entry_copy_maps circ_modifier/],
- acn => [qw/owning_lib record/],
- bre => [qw/flat_display_entries/],
- ascecm => [qw/stat_cat stat_cat_entry/],
- }
- }])->[0];
-
- return undef unless $item;
-
- my $details = {item => $item};
-
- $details->{circ} = $e->search_action_circulation([{
- target_copy => $item->id,
- checkin_time => undef,
- '-or' => [
- {stop_fines => undef},
- {stop_fines => [qw/MAXFINES LONGOVERDUE/]},
- ]
- }, {
- flesh => 2,
- flesh_fields => {circ => ['usr'], au => ['card']}
- }])->[0];
-
- if ($details->{circ}) {
-
- my $due_date = DateTime::Format::ISO8601->new->
- parse_datetime(clean_ISO8601($details->{circ}->due_date));
-
- $details->{due_date} =
- $instconf->{due_date_use_sip_date_format} ?
- sipdate($due_date) :
- $due_date->strftime('%F %T');
- }
-
-
- if ($item->status->id == OILS_COPY_STATUS_IN_TRANSIT) {
- $details->{transit} = $e->search_action_transit_copy([{
- target_copy => $item->id,
- dest_recv_time => undef,
- cancel_time => undef
- },{
- flesh => 1,
- flesh_fields => {atc => ['dest']}
- }])->[0];
- }
-
- if ($item->status->id == OILS_COPY_STATUS_ON_HOLDS_SHELF || (
- $details->{transit} &&
- $details->{transit}->copy_status == OILS_COPY_STATUS_ON_HOLDS_SHELF)) {
-
- $details->{hold} = $e->search_action_hold_request([{
- current_copy => $item->id,
- capture_time => {'!=' => undef},
- cancel_time => undef,
- fulfillment_time => undef
- }, {
- limit => 1,
- flesh => 1,
- flesh_fields => {ahr => ['pickup_lib']}
- }])->[0];
- }
-
-
- if ($details->{hold}) {
- my $pickup_date = $details->{hold}->shelf_expire_time;
- $details->{hold_pickup_date} =
- $pickup_date ? sipdate($pickup_date) : undef;
- }
-
- my ($title_entry) = grep {$_->name eq 'title'}
- @{$item->call_number->record->flat_display_entries};
-
- $details->{title} = $title_entry ? $title_entry->value : '';
-
- # Same as ../SIP*
- $details->{hold_queue_length} = $details->{hold} ? 1 : 0;
-
- $details->{circ_status} = circulation_status($item->status->id);
-
- $details->{fee_type} =
- ($item->deposit_amount > 0.0 && $item->deposit eq 'f') ?
- '06' : '01';
-
- my $cmod = $item->circ_modifier;
- $details->{magnetic_media} = $cmod && $cmod->magnetic_media eq 't';
- $details->{media_type} = $cmod ? $cmod->sip2_media_type : '001';
-
- return $details;
-}
-
-# Maps item status to SIP circulation status constants.
-sub circulation_status {
- my $stat = shift;
-
- return '02' if $stat == OILS_COPY_STATUS_ON_ORDER;
- return '03' if $stat == OILS_COPY_STATUS_AVAILABLE;
- return '04' if $stat == OILS_COPY_STATUS_CHECKED_OUT;
- return '06' if $stat == OILS_COPY_STATUS_IN_PROCESS;
- return '08' if $stat == OILS_COPY_STATUS_ON_HOLDS_SHELF;
- return '09' if $stat == OILS_COPY_STATUS_RESHELVING;
- return '10' if $stat == OILS_COPY_STATUS_IN_TRANSIT;
- return '12' if (
- $stat == OILS_COPY_STATUS_LOST ||
- $stat == OILS_COPY_STATUS_LOST_AND_PAID
- );
- return '13' if $stat == OILS_COPY_STATUS_MISSING;
-
- return '01';
-}
-
-1;
+++ /dev/null
-# ---------------------------------------------------------------
-# Copyright (C) 2020 King County Library System
-# Bill Erickson <berickxx@gmail.com>
-#
-# This program 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 2
-# of the License, or (at your option) any later version.
-
-# This program 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.
-#
-# ---------------------------------------------------------------
-# Code borrows heavily and sometimes copies directly from from
-# ../SIP* and SIPServer*
-# ---------------------------------------------------------------
-package OpenILS::WWW::SIP2Gateway::Patron;
-use strict; use warnings;
-use DateTime;
-use DateTime::Format::ISO8601;
-use OpenSRF::System;
-use OpenILS::Utils::CStoreEditor q/:funcs/;
-use OpenSRF::Utils::Logger q/$logger/;
-use OpenILS::Application::AppUtils;
-use OpenILS::Utils::DateTime qw/:datetime/;
-use OpenILS::Const qw/:const/;
-my $U = 'OpenILS::Application::AppUtils';
-
-sub get_patron_details {
- my ($class, %params) = @_;
-
- my $session = $params{session};
- my $instconf = $params{instconf};
- my $barcode = $params{barcode};
- my $password = $params{password};
-
- my $e = new_editor();
- my $details = {};
-
- my $card = $e->search_actor_card([{
- barcode => $barcode
- }, {
- flesh => 3,
- flesh_fields => {
- ac => [qw/usr/],
- au => [qw/
- billing_address
- mailing_address
- profile
- stat_cat_entries
- /],
- actscecm => [qw/stat_cat/]
- }
- }])->[0];
-
- my $patron = $details->{patron} = $card->usr;
- $patron->card($card);
-
- # We only attempt to verify the password if one is provided.
- return undef if defined $password &&
- !$U->verify_migrated_user_password($e, $patron->id, $password);
-
- my $penalties = get_patron_penalties($session, $patron);
-
- set_patron_privileges($session, $instconf, $details, $penalties);
-
- $details->{too_many_overdue} = 1 if
- grep {$_->{id} == OILS_PENALTY_PATRON_EXCEEDS_OVERDUE_COUNT}
- @$penalties;
-
- $details->{too_many_fines} = 1 if
- grep {$_->{id} == OILS_PENALTY_PATRON_EXCEEDS_FINES}
- @$penalties;
-
- my $summary = $e->retrieve_money_open_user_summary($patron->id);
- $details->{balance_owed} = ($summary) ? $summary->balance_owed : 0;
-
- set_patron_summary_items($session, $instconf, $details, %params);
- set_patron_summary_list_items($session, $instconf, $details, %params);
-
- return $details;
-}
-
-
-# Sets:
-# holds_count
-# overdue_count
-# out_count
-# fine_count
-# recall_count
-# unavail_holds_count
-sub set_patron_summary_items {
- my ($session, $instconf, $details, %params) = @_;
-
- my $patron = $details->{patron};
- my $e = new_editor();
-
- $details->{recall_count} = 0; # not supported
-
- my $hold_ids = get_hold_ids($e, $instconf, $patron);
- $details->{holds_count} = scalar(@$hold_ids);
-
- my $unavail_hold_ids = get_hold_ids($e, $instconf, $patron, 1);
- $details->{unavail_holds_count} = scalar(@$unavail_hold_ids);
-
- $details->{overdue_count} = 0;
- $details->{out_count} = 0;
-
- my $circ_summary = $e->retrieve_action_open_circ_list($patron->id);
- if ($circ_summary) { # undef if no circs for user
- my $overdue_ids = [ grep {$_ > 0} split(',', $circ_summary->overdue) ];
- my $out_ids = [ grep {$_ > 0} split(',', $circ_summary->out) ];
- $details->{overdue_count} = scalar(@$overdue_ids);
- $details->{out_count} = scalar(@$out_ids) + scalar(@$overdue_ids);
- }
-
- my $xacts = $U->simplereq(
- 'open-ils.actor',
- 'open-ils.actor.user.transactions.history.have_balance',
- $session->ils_authtoken,
- $patron->id
- );
-
- $details->{fine_count} = scalar(@$xacts);
-}
-
-sub get_hold_ids {
- my ($e, $instconf, $patron, $unavail, $offset, $limit) = @_;
-
- my $holds_where = {
- usr => $patron->id,
- fulfillment_time => undef,
- cancel_time => undef
- };
-
- if ($unavail) {
- $holds_where->{'-or'} = [
- {current_shelf_lib => undef},
- {current_shelf_lib => {'!=' => {'+ahr' => 'pickup_lib'}}}
- ];
-
- } else {
-
- $holds_where->{current_shelf_lib} = {'=' => {'+ahr' => 'pickup_lib'}}
- if $instconf->{msg64_hold_items_available};
- }
-
- my $query = {
- select => {ahr => ['id']},
- from => 'ahr',
- where => {'+ahr' => $holds_where}
- };
-
- $query->{offset} = $offset if $offset;
- $query->{limit} = $limit if $limit;
-
- my $id_hashes = $e->json_query($query);
-
- return [map {$_->{id}} @$id_hashes];
-}
-
-sub set_patron_summary_list_items {
- my ($session, $instconf, $details, %params) = @_;
- my $e = new_editor();
-
- my $list_items = $params{summary_list_items};
- my $offset = $params{summary_start_item} || 0;
- my $end_item = $params{summary_end_item} || 10;
- my $limit = $end_item - $offset;
-
- add_hold_items($e, $session, $instconf, $details, $offset, $limit)
- if $list_items eq 'hold_items';
-}
-
-sub add_hold_items {
- my ($e, $session, $instconf, $details, $offset, $limit) = @_;
-
- my $patron = $details->{patron};
- my $format = $instconf->{msg64_hold_datatype} || '';
- my $hold_ids = get_hold_ids($e, $instconf, $patron, 0, $offset, $limit);
-
- my @hold_items;
- for my $hold_id (@$hold_ids) {
- my $hold = $e->retrieve_action_hold_request($hold_id);
-
- if ($format eq 'barcode') {
- my $copy = find_copy_for_hold($e, $hold);
- push(@hold_items, $copy->barcode) if $copy;
- } else {
- my $title = find_title_for_hold($e, $hold);
- push(@hold_items, $title) if $title;
- }
- }
-
- $details->{hold_items} = \@hold_items;
-}
-
-# Hold -> reporter.hold_request_record -> display field for title.
-sub find_title_for_hold {
- my ($e, $hold) = @_;
-
- my $bib_link = $e->retrieve_reporter_hold_request_record($hold->id);
-
- my $title_field = $e->search_metabib_flat_display_entry({
- source => $bib_link->bib_record, name => 'title'})->[0];
-
- return $title_field ? $title_field->value : '';
-}
-
-# Finds a representative copy for the given hold. If no copy exists at
-# all, undef is returned. The only limit placed on what constitutes a
-# "representative" copy is that it cannot be deleted. Otherwise, any
-# copy that allows us to find the hold later is good enough.
-sub find_copy_for_hold {
- my ($e, $hold) = @_;
-
- return $e->retrieve_asset_copy($hold->current_copy)
- if $hold->current_copy;
-
- return $e->retrieve_asset_copy($hold->target)
- if $hold->hold_type =~ /C|R|F/;
-
- return $e->search_asset_copy([
- {call_number => $hold->target, deleted => 'f'},
- {limit => 1}])->[0] if $hold->hold_type eq 'V';
-
- my $bre_ids = [$hold->target];
-
- if ($hold->hold_type eq 'M') {
- # find all of the bibs that link to the target metarecord
- my $maps = $e->search_metabib_metarecord_source_map(
- {metarecord => $hold->target});
- $bre_ids = [map {$_->record} @$maps];
- }
-
- my $vol_ids = $e->search_asset_call_number(
- {record => $bre_ids, deleted => 'f'},
- {idlist => 1}
- );
-
- return $e->search_asset_copy([
- {call_number => $vol_ids, deleted => 'f'},
- {limit => 1}
- ])->[0];
-}
-
-
-sub set_patron_privileges {
- my ($session, $instconf, $details, $penalties) = @_;
- my $patron = $details->{patron};
-
- my $expire = DateTime::Format::ISO8601->new
- ->parse_datetime(clean_ISO8601($patron->expire_date));
-
- if ($expire < DateTime->now) {
- $logger->info("SIP2 Patron account is expired; all privileges blocked");
- $details->{charge_denied} = 1;
- $details->{recall_denied} = 1;
- $details->{renew_denied} = 1;
- $details->{holds_denied} = 1;
- return;
- }
-
- # Non-expired patrons are allowed all privileges when
- # patron_status_permit_all is true.
- return if $instconf->{patron_status_permit_all};
-
- my $blocked = (
- $patron->barred eq 't'
- || $patron->active eq 'f'
- || $patron->card->active eq 'f'
- );
-
- my @block_tags = map {$_->{block_list}} grep {$_->{block_list}} @$penalties;
-
- return unless $blocked || @block_tags; # no blocks remain
-
- $details->{holds_denied} = ($blocked || grep {$_ =~ /HOLD/} @block_tags);
-
- # Ignore loan-related blocks?
- return if $instconf->{patron_status_permit_loans};
-
- $details->{charge_denied} = ($blocked || grep {$_ =~ /CIRC/} @block_tags);
- $details->{renew_denied} = ($blocked || grep {$_ =~ /RENEW/} @block_tags);
-
- # In evergreen, patrons cannot create Recall holds directly, but that
- # doesn't mean they would not have said privilege if the functionality
- # existed. Base the ability to perform recalls on whether they have
- # checkout and holds privilege, since both would be needed for recalls.
- $details->{recall_denied} =
- ($details->{charge_denied} || $details->{holds_denied});
-}
-
-# Returns an array of penalty hashes with keys "id" and "block_list"
-sub get_patron_penalties {
- my ($session, $patron) = @_;
-
- return new_editor()->json_query({
- select => {csp => ['id', 'block_list']},
- from => {ausp => 'csp'},
- where => {
- '+ausp' => {
- usr => $patron->id,
- '-or' => [
- {stop_date => undef},
- {stop_date => {'>' => 'now'}}
- ],
- org_unit =>
- $U->get_org_full_path($session->editor->requestor->ws_ou)
- }
- }
- });
-}
-
-
-
-
-1;
--- /dev/null
+# ---------------------------------------------------------------
+# Copyright (C) 2020 King County Library System
+# Bill Erickson <berickxx@gmail.com>
+#
+# This program 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 2
+# of the License, or (at your option) any later version.
+
+# This program 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.
+#
+# ---------------------------------------------------------------
+# Code borrows heavily and sometimes copies directly from from
+# ../SIP* and SIPServer*
+# ---------------------------------------------------------------
+package OpenILS::WWW::SIP2Mediator;
+use strict; use warnings;
+use Apache2::Const -compile =>
+ qw(OK FORBIDDEN NOT_FOUND HTTP_INTERNAL_SERVER_ERROR HTTP_BAD_REQUEST);
+use Apache2::RequestRec;
+use CGI;
+use JSON::XS;
+use OpenSRF::System;
+use OpenSRF::Utils::Logger q/$logger/;
+use OpenILS::Application::AppUtils;
+my $U = 'OpenILS::Application::AppUtils';
+
+my $json = JSON::XS->new;
+$json->ascii(1);
+$json->allow_nonref(1);
+
+my $osrf_config;
+sub import {
+ $osrf_config = shift;
+}
+
+my $init_complete = 0;
+sub init {
+ return if $init_complete;
+ $init_complete = 1;
+ OpenSRF::System->bootstrap_client(config_file => $osrf_config);
+}
+
+sub handler {
+ my $r = shift;
+ my $cgi = CGI->new;
+ my ($message, $msg_code);
+
+ init();
+
+ my $seskey = $cgi->param('session');
+ my $msg_json = $cgi->param('message');
+
+ return Apache2::Const::FORBIDDEN unless $seskey;
+
+ if ($msg_json) {
+ eval { $message = $json->decode($msg_json) };
+ if ($message) {
+ $msg_code = $message->{code};
+ } else {
+ $logger->error("SIP2: Error parsing message JSON: $@ : $msg_json");
+ }
+ }
+
+ return Apache2::Const::HTTP_BAD_REQUEST unless $msg_code;
+
+ my $response = $U->simplereq(
+ 'open-ils.sip2',
+ 'open-ils.sip2.request', $seskey, $message);
+
+ if (my $textcode = $response->{textcode}) {
+ # we got an event instead of a SIP response
+ $logger->error("SIP2: Request returned $textcode: $msg_json");
+ # TODO: Could return more context-specific responses.
+ return Apache2::Const::HTTP_BAD_REQUEST;
+ }
+
+ $r->content_type('application/json');
+ $r->print($json->encode($response));
+
+ return Apache2::Const::OK;
+}
+
+1;