migrating top sip2 service
authorBill Erickson <berickxx@gmail.com>
Thu, 3 Sep 2020 19:57:20 +0000 (15:57 -0400)
committerBill Erickson <berickxx@gmail.com>
Wed, 28 Oct 2020 18:57:39 +0000 (14:57 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/examples/apache_24/eg_vhost.conf.in
Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/SIP2Gateway.pm [deleted file]
Open-ILS/src/perlmods/lib/OpenILS/WWW/SIP2Gateway/Item.pm [deleted file]
Open-ILS/src/perlmods/lib/OpenILS/WWW/SIP2Gateway/Patron.pm [deleted file]
Open-ILS/src/perlmods/lib/OpenILS/WWW/SIP2Mediator.pm [new file with mode: 0644]

index 66e5802..00d4f46 100644 (file)
@@ -612,7 +612,7 @@ RewriteRule ^/conify/([a-z]{2}-[A-Z]{2})/global/(.*)$ /conify/global/$2 [E=local
 
 <Location /sip2-mediator>
     SetHandler perl-script
-    PerlHandler OpenILS::WWW::SIP2Gateway
+    PerlHandler OpenILS::WWW::SIP2Mediator
     Options +ExecCGI
     Require all granted
 </Location>
index b7ce0b5..4a572ea 100644 (file)
@@ -210,8 +210,8 @@ sub handle_patron_info {
     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] : '';
@@ -236,12 +236,12 @@ sub handle_patron_info {
 
     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 
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/SIP2Gateway.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/SIP2Gateway.pm
deleted file mode 100644 (file)
index 86510c5..0000000
+++ /dev/null
@@ -1,611 +0,0 @@
-# ---------------------------------------------------------------
-# 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;
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/SIP2Gateway/Item.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/SIP2Gateway/Item.pm
deleted file mode 100644 (file)
index 90a4401..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-# ---------------------------------------------------------------
-# 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;
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/SIP2Gateway/Patron.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/SIP2Gateway/Patron.pm
deleted file mode 100644 (file)
index 5ab3faf..0000000
+++ /dev/null
@@ -1,320 +0,0 @@
-# ---------------------------------------------------------------
-# 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;
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/SIP2Mediator.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/SIP2Mediator.pm
new file mode 100644 (file)
index 0000000..9e0f18a
--- /dev/null
@@ -0,0 +1,87 @@
+# ---------------------------------------------------------------
+# 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;