From a19bf2493ef737f67f90b26a9d404e7934344e6e Mon Sep 17 00:00:00 2001 From: Thomas Berezansky Date: Tue, 20 Jan 2015 12:42:53 -0500 Subject: [PATCH] LP#1413624: OpenILS::WWW::AccessHandler Module Perl module compatible with Apache PerlAccessHandler directives. Includes Release Notes and basic documentation (in the form of a mildly edited copy of the release notes) Signed-off-by: Thomas Berezansky Signed-off-by: Galen Charlton --- .../perlmods/lib/OpenILS/WWW/AccessHandler.pm | 216 ++++++++++++++++++ .../Administration/apache_access_handler.txt | 139 +++++++++++ docs/TechRef/Apache/apache_access_handler.txt | 139 +++++++++++ 3 files changed, 494 insertions(+) create mode 100644 Open-ILS/src/perlmods/lib/OpenILS/WWW/AccessHandler.pm create mode 100644 docs/RELEASE_NOTES_NEXT/Administration/apache_access_handler.txt create mode 100644 docs/TechRef/Apache/apache_access_handler.txt diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/AccessHandler.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/AccessHandler.pm new file mode 100644 index 0000000000..e9227e4cff --- /dev/null +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/AccessHandler.pm @@ -0,0 +1,216 @@ +package OpenILS::WWW::AccessHandler; +use strict; use warnings; + +# Apache Requirements +use Apache2::Const -compile => qw(:common HTTP_OK HTTP_FORBIDDEN HTTP_MOVED_TEMPORARILY HTTP_MOVED_PERMANENTLY); +use Apache2::RequestRec; +use Apache2::URI; + +# OpenSRF requirements +use OpenSRF::System; + +# Other requirements +use URI::Escape; + +# Auth Handler +sub handler { + my ($r) = @_; + + # Configuration options + + # URL to redirect to for login + my $lurl = $r->dir_config('OILSAccessHandlerLoginURL') || '/eg/opac/login'; + # Redirection variable to come "back" from the previous URL + my $lurlvar = $r->dir_config('OILSAccessHandlerLoginURLRedirectVar') || 'redirect_to'; + # No access page? If not set, just return forbidden. + my $failurl = $r->dir_config('OILSAccessHandlerFailURL'); + # Required permission (if set) + my $userperm = $r->dir_config('OILSAccessHandlerPermission'); + # Need to be in good standing? + my $userstanding = $r->dir_config('OILSAccessHandlerGoodStanding') || 0; + # Required org unit (if set) + my $userou = $r->dir_config('OILSAccessHandlerHomeOU'); + # OU for permission/standing checks, if not set use home OU + my $checkou = $r->dir_config('OILSAccessHandlerCheckOU'); + # Set referrer based on OU Setting? + my $referrersetting = $r->dir_config('OILSAccessHandlerReferrerSetting'); + + my $url = $r->construct_url(); + + # push everyone to the secure site + if ($url =~ /^http:/o) { + my $target = $r->construct_url($r->unparsed_uri); + $target =~ s/^http:/https:/o; + $r->headers_out->set(Location => $target); + return Apache2::Const::HTTP_MOVED_PERMANENTLY; + } + + # We could use CGI....but that creates issues if post data may be submitted + my $auth_ses = ($r->headers_in->get('Cookie') =~ /(?:^|\s)ses=([^;]*)/)[0]; + my $user = _verify_login($auth_ses); + + if (!defined($user)) { + my $redirect = $r->construct_url($r->unparsed_uri); + my $target = $r->construct_url($lurl) . '?' . $lurlvar . '=' . uri_escape($redirect); + $target =~ s/^http:/https:/o; # This should never be needed due to the redirect above, but better safe than sorry + $r->headers_out->set(Location => $target); + # Lets not cache this either, just in case. + $r->headers_out->set('Cache-Control' => 'no-cache, no-store, must-revalidate'); + $r->headers_out->set(Pragma => 'no-cache'); + $r->headers_out->set(Expires => 0); + return Apache2::Const::HTTP_MOVED_TEMPORARILY; + } + + # Convert check OU from shortname, if needed + $checkou = _get_org_id($checkou); + + # If we have no check OU at this point, use the user's home OU + $checkou ||= $user->home_ou; + + my $failed = 0; + + if ($userperm) { + my @permissions = split(/\s*,\s*/, $userperm); + $failed++ unless _verify_permission($auth_ses, $user, $checkou, \@permissions); + } + if (!$failed && $userstanding) { + $failed++ unless _verify_standing($user); + } + if (!$failed && $userou) { + $failed++ unless _verify_home_ou($user, $userou); + } + + # If we failed one of the above checks they aren't allowed in + if ($failed > 0) { + if ($failurl) { + my $target = $r->construct_url($failurl); + $r->headers_out->set(Location => $target); + return Apache2::Const::HTTP_MOVED_TEMPORARILY; + } else { + return Apache2::Const::HTTP_FORBIDDEN; + } + } + + # Forced referrer for some referrer auth services? + if ($referrersetting) { + my $referrervalue = _get_ou_setting($referrersetting, $checkou); + if ($referrervalue && $referrervalue->{value}) { + $r->headers_in->set('Referer', $referrervalue->{value}); + } + } + + # If we haven't thrown them out yet, let them through + return Apache2::Const::OK; +} + +# "Private" functions + +# Verify our login +sub _verify_login { + my ($token) = @_; + return undef unless $token; + + my $user = OpenSRF::AppSession + ->create('open-ils.auth') + ->request('open-ils.auth.session.retrieve', $token) + ->gather(1); + + if (ref($user) eq 'HASH' && $user->{ilsevent} == 1001) { + return undef; + } + + return $user if ref($user); + return undef; +} + +# OU Shortname to ID +sub _get_org_id { + my ($org_identifier) = @_; + # Does this look like a number? + if ($org_identifier !~ '^[0-9]+$') { + # Not a database id + if ($org_identifier) { + # Look up org unit by shortname + my $org_unit = OpenSRF::AppSession + ->create('open-ils.actor') + ->request('open-ils.actor.org_unit.retrieve_by_shortname', $org_identifier) + ->gather(1); + if ($org_unit && ref($org_unit) != 'HASH') { + # We appear to have an org unit! So return the ID. + return $org_unit->id; + } + } + } else { + # Looks like a database ID, so leave it alone. + return $org_identifier; + } + # If we have reached this point, assume that we found no useful ID. + return undef; +} + +# Verify home OU +sub _verify_home_ou { + my ($user, $home_ou) = @_; + my $org_tree = OpenSRF::AppSession + ->create('open-ils.actor') + ->request('open-ils.actor.org_tree.ancestors.retrieve', $user->home_ou) + ->gather(1); + if ($org_tree && ref($org_tree) ne 'HASH') { + my %user_orgs; + do { + $user_orgs{$org_tree->id} = 1; + if ($org_tree->children) { + $org_tree = @{$org_tree->children}[0]; + } else { + $org_tree = undef; + } + } while ($org_tree); + + my @home_ous = split(/\s*,\s*/,$home_ou); + for my $cur_ou (@home_ous) { + $cur_ou = _get_org_id($cur_ou); + if ($user_orgs{$cur_ou}) { + return 1; + } + } + } + return 0; +} + +# Verify permission +sub _verify_permission { + my ($token, $user, $org_unit, $permissions) = @_; + + my $failures = OpenSRF::AppSession + ->create('open-ils.actor') + ->request('open-ils.actor.user.perm.check', $token, $user->id, $org_unit, $permissions) + ->gather(1); + + return !scalar(@$failures); +} + +# Verify standing +sub _verify_standing { + my ($user) = @_; + + # If barred you are not in good standing + return 0 if $user->barred; + # If inactive you are also not in good standing + return 0 unless $user->active; + + # Possible addition: Standing Penalty Checks? + + return 1; +} + +sub _get_ou_setting { + my ($setting, $org_unit) = @_; + + my $value = OpenSRF::AppSession->create('open-ils.actor') + ->request('open-ils.actor.ou_setting.ancestor_default', $org_unit, $setting) + ->gather(1); + + return $value; +} + +1; diff --git a/docs/RELEASE_NOTES_NEXT/Administration/apache_access_handler.txt b/docs/RELEASE_NOTES_NEXT/Administration/apache_access_handler.txt new file mode 100644 index 0000000000..3e35ed37d9 --- /dev/null +++ b/docs/RELEASE_NOTES_NEXT/Administration/apache_access_handler.txt @@ -0,0 +1,139 @@ +Apache Access Handler: OpenILS::WWW::AccessHandler +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +This Perl module is intended for limiting patron access to configured locations +in Apache. These locations could be folder trees, static files, non-Evergreen +dynamic content, or other Apache features/modules. It is intended as a more +patron-oriented and transparent version of the OpenILS::WWW::Proxy and +OpenILS::WWW:Proxy::Authen modules. + +Instead of using Basic Authentication the AccessHandler module instead redirects +to the OPAC for login. Once logged in additional checks can be performed, based +on configured variables: + + * Permission Checks (at Home OU or specified location) + * Home OU Checks (Org Unit or Descendant) + * "Good standing" Checks (Not Inactive or Barred) + +Use of the module is a simple addition to a Location block in Apache: + +[source,conf] + + PerlAccessHandler OpenILS::WWW::AccessHandler + # For each option you wish to set: + PerlSetVar OPTION "VALUE" + + +The available options are: + +OILSAccessHandlerLoginURL:: + Default: /eg/opac/login + + The page to redirect to when Login is needed +OILSAccessHandlerLoginURLRedirectVar:: + Default: redirect_to + + The variable the login page wants the "destination" URL stored in +OILSAccessHandlerFailURL:: + Default: + + URL to go to if Permission, Good Standing, or Home OU checks fail. If not set + a 403 error is generated instead. To customize the 403 you could use an + ErrorDocument statement. +OILSAccessHandlerCheckOU:: + Default: + + Org Unit to check Permissions at and/or to load Referrer from. Can be a + shortname or an ID. +OILSAccessHandlerPermission:: + Default: + + Permission, or comma delimited set of permissions, the user must have to + access the protected area. +OILSAccessHandlerGoodStanding:: + Default: 0 + + If set to a true value the user must be both Active and not Barred. +OILSAccessHandlerHomeOU:: + Default: + + An Org Unit, or comma delimited set of Org Units, that the user's Home OU must + be equal to or a descendant of to access this resource. Can be set to + shortnames or IDs. +OILSAccessHandlerReferrerSetting:: + Default: + + Library Setting to pull a forced referrer string out of, if set. + +As the AccessHandler module does not actually serve the content it is +protecting, but instead merely hands control back to Apache when it is done +authenticating, you can protect almost anything else you can serve with Apache. + +Use Cases ++++++++++ +The general use of this module is "protect access to something else" - what that +something else is will vary. Some possibilities: + + * Apache features + ** Automatic Directory Indexes + ** Proxies (see below) + *** Electronic Databases + *** Software on other servers/ports + * Non-Evergreen software + ** Timekeeping software for staff + ** Specialized patron request packages + * Static files and folders + ** Semi-public Patron resources + ** Staff-only downloads + +Proxying Websites ++++++++++++++++++ +One potentially interesting use of the AccessHandler module is to protect an +Apache Proxy configuration. For example, after installing and enabling +mod_proxy, mod_proxy_http, and mod_proxy_html you could proxy websites like so: + +[source,conf] +---- + + # Base "Rewrite URLs" configuration + ProxyHTMLLinks a href + ProxyHTMLLinks area href + ProxyHTMLLinks link href + ProxyHTMLLinks img src longdesc usemap + ProxyHTMLLinks object classid codebase data usemap + ProxyHTMLLinks q cite + ProxyHTMLLinks blockquote cite + ProxyHTMLLinks ins cite + ProxyHTMLLinks del cite + ProxyHTMLLinks form action + ProxyHTMLLinks input src usemap + ProxyHTMLLinks head profile + ProxyHTMLLinks base href + ProxyHTMLLinks script src for + + # To support scripting events (with ProxyHTMLExtended On) + ProxyHTMLEvents onclick ondblclick onmousedown onmouseup \ + onmouseover onmousemove onmouseout onkeypress \ + onkeydown onkeyup onfocus onblur onload \ + onunload onsubmit onreset onselect onchange + + # Limit all Proxy connections to authenticated sessions by default + PerlAccessHandler OpenILS::WWW::AccessHandler + + # Strip out Evergreen cookies before sending to remote server + RequestHeader edit Cookie "^(.*?)ses=.*?(?:$|;)(.*)$" $1$2 + RequestHeader edit Cookie "^(.*?)eg_loggedin=.*?(?:$|;)(.*)$" $1$2 + + + + # Proxy example.net + ProxyPass http://www.example.net/ + ProxyPassReverse http://www.example.net/ + ProxyPassReverseCookieDomain example.net example.com + ProxyPassReverseCookiePath / /proxy/example/ + + ProxyHTMLEnable On + ProxyHTMLURLMap http://www.example.net/ /proxy/example/ + ProxyHTMLURLMap / /proxy/mail/ + ProxyHTMLCharsetOut * + + # Limit to BR1 and BR3 users + PerlSetVar OILSAccessHandlerHomeOU "BR1,BR3" + +---- + +As mentioned above, this can be used for multiple reasons. In addition to +websites such as online databases for patron use you may wish to proxy software +for staff or patron use to make it appear on your catalog domain, or perhaps to +keep from needing to open extra ports in a firewall. diff --git a/docs/TechRef/Apache/apache_access_handler.txt b/docs/TechRef/Apache/apache_access_handler.txt new file mode 100644 index 0000000000..08bf7b6d2b --- /dev/null +++ b/docs/TechRef/Apache/apache_access_handler.txt @@ -0,0 +1,139 @@ +Apache Access Handler: OpenILS::WWW::AccessHandler +-------------------------------------------------- +This Perl module is intended for limiting patron access to configured locations +in Apache. These locations could be folder trees, static files, non-Evergreen +dynamic content, or other Apache features/modules. It is intended as a more +patron-oriented and transparent version of the OpenILS::WWW::Proxy and +OpenILS::WWW:Proxy::Authen modules. + +Instead of using Basic Authentication the AccessHandler module instead redirects +to the OPAC for login. Once logged in additional checks can be performed, based +on configured variables: + + * Permission Checks (at Home OU or specified location) + * Home OU Checks (Org Unit or Descendant) + * "Good standing" Checks (Not Inactive or Barred) + +Use of the module is a simple addition to a Location block in Apache: + +[source,conf] + + PerlAccessHandler OpenILS::WWW::AccessHandler + # For each option you wish to set: + PerlSetVar OPTION "VALUE" + + +The available options are: + +OILSAccessHandlerLoginURL:: + Default: /eg/opac/login + + The page to redirect to when Login is needed +OILSAccessHandlerLoginURLRedirectVar:: + Default: redirect_to + + The variable the login page wants the "destination" URL stored in +OILSAccessHandlerFailURL:: + Default: + + URL to go to if Permission, Good Standing, or Home OU checks fail. If not set + a 403 error is generated instead. To customize the 403 you could use an + ErrorDocument statement. +OILSAccessHandlerCheckOU:: + Default: + + Org Unit to check Permissions at and/or to load Referrer from. Can be a + shortname or an ID. +OILSAccessHandlerPermission:: + Default: + + Permission, or comma delimited set of permissions, the user must have to + access the protected area. +OILSAccessHandlerGoodStanding:: + Default: 0 + + If set to a true value the user must be both Active and not Barred. +OILSAccessHandlerHomeOU:: + Default: + + An Org Unit, or comma delimited set of Org Units, that the user's Home OU must + be equal to or a descendant of to access this resource. Can be set to + shortnames or IDs. +OILSAccessHandlerReferrerSetting:: + Default: + + Library Setting to pull a forced referrer string out of, if set. + +As the AccessHandler module does not actually serve the content it is +protecting, but instead merely hands control back to Apache when it is done +authenticating, you can protect almost anything else you can serve with Apache. + +Use Cases +~~~~~~~~~ +The general use of this module is "protect access to something else" - what that +something else is will vary. Some possibilities: + + * Apache features + ** Automatic Directory Indexes + ** Proxies (see below) + *** Electronic Databases + *** Software on other servers/ports + * Non-Evergreen software + ** Timekeeping software for staff + ** Specialized patron request packages + * Static files and folders + ** Semi-public Patron resources + ** Staff-only downloads + +Proxying Websites +~~~~~~~~~~~~~~~~~ +One potentially interesting use of the AccessHandler module is to protect an +Apache Proxy configuration. For example, after installing and enabling +mod_proxy, mod_proxy_http, and mod_proxy_html you could proxy websites like so: + +[source,conf] +---- + + # Base "Rewrite URLs" configuration + ProxyHTMLLinks a href + ProxyHTMLLinks area href + ProxyHTMLLinks link href + ProxyHTMLLinks img src longdesc usemap + ProxyHTMLLinks object classid codebase data usemap + ProxyHTMLLinks q cite + ProxyHTMLLinks blockquote cite + ProxyHTMLLinks ins cite + ProxyHTMLLinks del cite + ProxyHTMLLinks form action + ProxyHTMLLinks input src usemap + ProxyHTMLLinks head profile + ProxyHTMLLinks base href + ProxyHTMLLinks script src for + + # To support scripting events (with ProxyHTMLExtended On) + ProxyHTMLEvents onclick ondblclick onmousedown onmouseup \ + onmouseover onmousemove onmouseout onkeypress \ + onkeydown onkeyup onfocus onblur onload \ + onunload onsubmit onreset onselect onchange + + # Limit all Proxy connections to authenticated sessions by default + PerlAccessHandler OpenILS::WWW::AccessHandler + + # Strip out Evergreen cookies before sending to remote server + RequestHeader edit Cookie "^(.*?)ses=.*?(?:$|;)(.*)$" $1$2 + RequestHeader edit Cookie "^(.*?)eg_loggedin=.*?(?:$|;)(.*)$" $1$2 + + + + # Proxy example.net + ProxyPass http://www.example.net/ + ProxyPassReverse http://www.example.net/ + ProxyPassReverseCookieDomain example.net example.com + ProxyPassReverseCookiePath / /proxy/example/ + + ProxyHTMLEnable On + ProxyHTMLURLMap http://www.example.net/ /proxy/example/ + ProxyHTMLURLMap / /proxy/mail/ + ProxyHTMLCharsetOut * + + # Limit to BR1 and BR3 users + PerlSetVar OILSAccessHandlerHomeOU "BR1,BR3" + +---- + +As mentioned above, this can be used for multiple reasons. In addition to +websites such as online databases for patron use you may wish to proxy software +for staff or patron use to make it appear on your catalog domain, or perhaps to +keep from needing to open extra ports in a firewall. -- 2.43.2