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 <tsbere@mvlc.org>
Signed-off-by: Galen Charlton <gmc@esilibrary.com>
--- /dev/null
+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;
--- /dev/null
+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]
+<Location /path/to/be/protected>
+ PerlAccessHandler OpenILS::WWW::AccessHandler
+ # For each option you wish to set:
+ PerlSetVar OPTION "VALUE"
+</Location>
+
+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: <unset> +
+ 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: <User Home OU> +
+ Org Unit to check Permissions at and/or to load Referrer from. Can be a
+ shortname or an ID.
+OILSAccessHandlerPermission::
+ Default: <unset> +
+ 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: <unset> +
+ 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: <unset> +
+ 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]
+----
+<Location /proxy/>
+ # 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
+</Location>
+
+<Location /proxy/example/>
+ # 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"
+</Location>
+----
+
+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.
--- /dev/null
+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]
+<Location /path/to/be/protected>
+ PerlAccessHandler OpenILS::WWW::AccessHandler
+ # For each option you wish to set:
+ PerlSetVar OPTION "VALUE"
+</Location>
+
+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: <unset> +
+ 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: <User Home OU> +
+ Org Unit to check Permissions at and/or to load Referrer from. Can be a
+ shortname or an ID.
+OILSAccessHandlerPermission::
+ Default: <unset> +
+ 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: <unset> +
+ 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: <unset> +
+ 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]
+----
+<Location /proxy/>
+ # 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
+</Location>
+
+<Location /proxy/example/>
+ # 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"
+</Location>
+----
+
+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.