From 5e8d3260127520aedf25759e361f3f22dd46d02f Mon Sep 17 00:00:00 2001 From: Nicholas van Oudtshoorn Date: Mon, 30 Oct 2017 16:23:04 +0800 Subject: [PATCH] Bug 14407 - Allow restricting SCO to IP or IP range Fix presence of tabs in files, and printslip refering to $query instead of $input Modified so that if invalid ranges or IPs are given, access is simply denied: it fails secure. Includes unit tests of the new in_ipset function. Sponsored-by: Perth Bible College --- C4/Auth.pm | 26 ++++++++++++++- C4/Installer/PerlDependencies.pm | 5 +++ ..._14407-add_selfcheckallowbyipranges_syspref.sql | 2 ++ installer/data/mysql/sysprefs.sql | 1 + .../en/modules/admin/preferences/circulation.pref | 4 +++ opac/sco/help.pl | 8 ++++- opac/sco/printslip.pl | 7 +++- opac/sco/sco-main.pl | 10 ++++-- opac/sco/sco-patron-image.pl | 6 ++++ t/Auth.t | 37 ++++++++++++++++++++++ 10 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 installer/data/mysql/atomicupdate/bug_14407-add_selfcheckallowbyipranges_syspref.sql create mode 100644 t/Auth.t diff --git a/C4/Auth.pm b/C4/Auth.pm index 11f57b2..bcaaaeb 100644 --- a/C4/Auth.pm +++ b/C4/Auth.pm @@ -39,6 +39,7 @@ use Koha::Patrons; use POSIX qw/strftime/; use List::MoreUtils qw/ any /; use Encode qw( encode is_utf8); +use Net::CIDR; # use utf8; use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap $cas $caslogout $shib $shib_login); @@ -55,7 +56,7 @@ BEGIN { @ISA = qw(Exporter); @EXPORT = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions); @EXPORT_OK = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &checkpw_internal &checkpw_hash - &get_all_subpermissions &get_user_subpermissions + &get_all_subpermissions &get_user_subpermissions &in_ipset ); %EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] ); $ldap = C4::Context->config('useldapserver') || 0; @@ -2082,6 +2083,29 @@ sub haspermission { #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered. } +=head2 in_ipset + + $flags = ($ipset); + +C<$ipset> A space separated string describing an IP set. Can include single IPs or ranges + +Returns 1 if the remote address is in the provided ipset, or 0 otherwise. + +=cut + +sub in_ipset { + my ($ipset) = @_; + my @allowedipranges = split(' ', $ipset); + if (scalar @allowedipranges > 0) { + my @rangelist; + eval { @rangelist = Net::CIDR::range2cidr(@allowedipranges); }; return 0 if $@; + unless (Net::CIDR::cidrlookup($ENV{'REMOTE_ADDR'}, @rangelist)) { + return 0; + } + } + return 1; +} + sub getborrowernumber { my ($userid) = @_; my $userenv = C4::Context->userenv; diff --git a/C4/Installer/PerlDependencies.pm b/C4/Installer/PerlDependencies.pm index 3cacafc..0b08f34 100644 --- a/C4/Installer/PerlDependencies.pm +++ b/C4/Installer/PerlDependencies.pm @@ -802,6 +802,11 @@ our $PERL_DEPS = { 'required' => '0', 'min_ver' => '0.56', }, + 'Net::CIDR' => { + 'usage' => 'Core', + 'required' => '1', + 'min_ver' => '0.15', + }, 'Net::SFTP::Foreign' => { 'usage' => 'Edifact', 'required' => '0', diff --git a/installer/data/mysql/atomicupdate/bug_14407-add_selfcheckallowbyipranges_syspref.sql b/installer/data/mysql/atomicupdate/bug_14407-add_selfcheckallowbyipranges_syspref.sql new file mode 100644 index 0000000..4041a58 --- /dev/null +++ b/installer/data/mysql/atomicupdate/bug_14407-add_selfcheckallowbyipranges_syspref.sql @@ -0,0 +1,2 @@ +INSERT IGNORE INTO systempreferences (variable,value,explanation,options,type) VALUES + ('SelfCheckAllowByIPRanges','',NULL,'(Leave blank if not used. Use ranges or simple ip addresses separated by spaces, like 192.168.1.1 192.168.0.0/24.)','Short'); diff --git a/installer/data/mysql/sysprefs.sql b/installer/data/mysql/sysprefs.sql index f6e666b..b75f29c 100644 --- a/installer/data/mysql/sysprefs.sql +++ b/installer/data/mysql/sysprefs.sql @@ -475,6 +475,7 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('SearchEngine','Zebra','Elasticsearch|Zebra','Search Engine','Choice'), ('SearchMyLibraryFirst','0',NULL,'If ON, OPAC searches return results limited by the user\'s library by default if they are logged in','YesNo'), ('SearchWithISBNVariations','0',NULL,'If enabled, search on all variations of the ISBN','YesNo'), +('SelfCheckAllowByIPRanges','',NULL,'(Leave blank if not used. Use ranges or simple ip addresses separated by spaces, like 192.168.1.1 192.168.0.0/24.)','Short'), ('SelfCheckHelpMessage','','70|10','Enter HTML to include under the basic Web-based Self Checkout instructions on the Help page','Textarea'), ('SelfCheckoutByLogin','1',NULL,'Have patrons login into the web-based self checkout system with their username/password or their cardnumber','YesNo'), ('SelfCheckReceiptPrompt','1','NULL','If ON, print receipt dialog pops up when self checkout is finished','YesNo'), diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref index b77b373..e308290 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref @@ -824,6 +824,10 @@ Circulation: yes: Show no: "Don't show" - "the print receipt popup dialog when self checkout is finished" + - + - pref: SelfCheckAllowByIPRanges + class: short + - (Leave blank if not used. Use ranges or simple ip addresses separated by spaces, like 192.168.1.1 192.168.0.0/24.) Course Reserves: - - pref: UseCourseReserves diff --git a/opac/sco/help.pl b/opac/sco/help.pl index 412dc40..5c17cc6 100755 --- a/opac/sco/help.pl +++ b/opac/sco/help.pl @@ -25,10 +25,16 @@ use strict; use warnings; use CGI qw ( -utf8 ); -use C4::Auth qw(get_template_and_user); +use C4::Auth qw(get_template_and_user in_ipset); use C4::Output qw(output_html_with_http_headers); my $query = new CGI; + +unless ( in_ipset(C4::Context->preference('SelfCheckAllowByIPRanges')) ) { + print $query->redirect("/cgi-bin/koha/opac-main.pl"); + exit; +} + my ($template, $borrowernumber, $cookie) = get_template_and_user({ template_name => "sco/help.tt", query => $query, diff --git a/opac/sco/printslip.pl b/opac/sco/printslip.pl index b85b8f5..98e8981 100755 --- a/opac/sco/printslip.pl +++ b/opac/sco/printslip.pl @@ -30,12 +30,17 @@ use strict; use warnings; use CGI qw ( -utf8 ); use C4::Context; -use C4::Auth qw/:DEFAULT get_session/; +use C4::Auth qw/:DEFAULT get_session in_ipset/; use C4::Output; use C4::Members; use C4::Koha; my $input = new CGI; +unless ( in_ipset(C4::Context->preference('SelfCheckAllowByIPRanges')) ) { + print $input->header(status => '403 Forbidden - functionality not available from your location'); + exit; +} + my $sessionID = $input->cookie("CGISESSID"); my $session = get_session($sessionID); diff --git a/opac/sco/sco-main.pl b/opac/sco/sco-main.pl index d6e4176..d93fc07 100755 --- a/opac/sco/sco-main.pl +++ b/opac/sco/sco-main.pl @@ -35,7 +35,7 @@ use Modern::Perl; use CGI qw ( -utf8 ); -use C4::Auth qw(get_template_and_user checkpw); +use C4::Auth qw(get_template_and_user checkpw in_ipset); use C4::Koha; use C4::Circulation; use C4::Reserves; @@ -58,7 +58,13 @@ unless (C4::Context->preference('WebBasedSelfCheck')) { exit; } -if (C4::Context->preference('AutoSelfCheckAllowed')) +unless ( in_ipset(C4::Context->preference('SelfCheckAllowByIPRanges')) ) { + # redirect to OPAC home if self-checkout not permitted from current IP + print $query->redirect("/cgi-bin/koha/opac-main.pl"); + exit; +} + +if (C4::Context->preference('AutoSelfCheckAllowed')) { my $AutoSelfCheckID = C4::Context->preference('AutoSelfCheckID'); my $AutoSelfCheckPass = C4::Context->preference('AutoSelfCheckPass'); diff --git a/opac/sco/sco-patron-image.pl b/opac/sco/sco-patron-image.pl index dc6fbc8..6ee9a02 100755 --- a/opac/sco/sco-patron-image.pl +++ b/opac/sco/sco-patron-image.pl @@ -19,6 +19,7 @@ use strict; use warnings; +use C4::Auth qw(in_ipset); use C4::Service; use C4::Members; use Koha::Patron::Images; @@ -36,6 +37,11 @@ unless (C4::Context->preference('ShowPatronImageInWebBasedSelfCheck')) { exit; } +unless ( in_ipset(C4::Context->preference('SelfCheckAllowByIPRanges')) ) { + print $query->header(status => '403 Forbidden - functionality not available from your location'); + exit; +} + my ($borrowernumber) = C4::Service->require_params('borrowernumber'); my ($csrf_token) = C4::Service->require_params('csrf_token'); diff --git a/t/Auth.t b/t/Auth.t new file mode 100644 index 0000000..89dc064 --- /dev/null +++ b/t/Auth.t @@ -0,0 +1,37 @@ +# This file is part of Koha. +# +# Copyright (C) 2013 Equinox Software, Inc. +# +# Koha 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 3 of the License, or +# (at your option) any later version. +# +# Koha 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. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; +use Test::More tests => 10; + +use C4::Auth qw / in_ipset /; +use warnings; + +$ENV{REMOTE_ADDR} = '192.168.1.30'; +my $ipset1 = "192.168.1.30"; + +ok(in_ipset("192.168.1.30"), 'simple single ip matching remote ip'); +ok(!in_ipset("192.168.1.31"), 'simple single ip not match remote ip'); +ok(in_ipset("192.168.1.1/24"), 'simple ip range/24 with remote ip in it'); +ok(!in_ipset("192.168.2.1/24"), 'simple ip range/24 with remote ip not in it'); +ok(in_ipset("192.168.2.1/16"), 'simple ip range/16 with remote ip in it'); +ok(!in_ipset("192.168.1.10-30"), 'invalidly represented IP range with remote ip in it'); +ok(in_ipset("192.168.1.10-192.168.1.30"), 'validly represented ip range with remote ip in it'); +ok(in_ipset("127.0.0.1 192.168.1.30 192.168.2.10-192.168.2.25"), 'multiple ips and ranges, including the remote ip'); +ok(!in_ipset("127.0.0.1 8.8.8.8 192.168.2.1/24 192.168.3.1/24 192.168.1.1-192.168.1.29"), "multiple ip and ip ranges, with the remote ip in none of them"); +ok(in_ipset(""), "blank list given, no preference set - implies everything goes through."); + -- 2.7.5