LP#1413624: AccessHandler Space delimination and bugfix
[working/Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / WWW / AccessHandler.pm
1 package OpenILS::WWW::AccessHandler;
2 use strict; use warnings;
3
4 # Apache Requirements
5 use Apache2::Const -compile => qw(:common HTTP_OK HTTP_FORBIDDEN HTTP_MOVED_TEMPORARILY HTTP_MOVED_PERMANENTLY);
6 use Apache2::RequestRec;
7 use Apache2::URI;
8
9 # OpenSRF requirements
10 use OpenSRF::System;
11
12 # Other requirements
13 use URI::Escape;
14
15 # Auth Handler
16 sub handler {
17     my ($r) = @_;
18
19     # Configuration options
20
21     # URL to redirect to for login
22     my $lurl = $r->dir_config('OILSAccessHandlerLoginURL') || '/eg/opac/login';
23     # Redirection variable to come "back" from the previous URL
24     my $lurlvar = $r->dir_config('OILSAccessHandlerLoginURLRedirectVar') || 'redirect_to';
25     # No access page? If not set, just return forbidden.
26     my $failurl = $r->dir_config('OILSAccessHandlerFailURL');
27     # Required permission (if set)
28     my $userperm = $r->dir_config('OILSAccessHandlerPermission');
29     # Need to be in good standing?
30     my $userstanding = $r->dir_config('OILSAccessHandlerGoodStanding') || 0;
31     # Required org unit (if set)
32     my $userou = $r->dir_config('OILSAccessHandlerHomeOU');
33     # OU for permission/standing checks, if not set use home OU
34     my $checkou = $r->dir_config('OILSAccessHandlerCheckOU');
35     # Set referrer based on OU Setting?
36     my $referrersetting = $r->dir_config('OILSAccessHandlerReferrerSetting');
37
38     my $url = $r->construct_url();
39
40     # push everyone to the secure site
41     if ($url =~ /^http:/o) {
42         my $target = $r->construct_url($r->unparsed_uri);
43         $target =~ s/^http:/https:/o;
44         $r->headers_out->set(Location => $target);
45         return Apache2::Const::HTTP_MOVED_PERMANENTLY;
46     }
47
48     # We could use CGI....but that creates issues if post data may be submitted
49     my $auth_ses = ($r->headers_in->get('Cookie') =~ /(?:^|\s)ses=([^;]*)/)[0];
50     my $user = _verify_login($auth_ses);
51
52     if (!defined($user)) {
53         my $redirect = $r->construct_url($r->unparsed_uri);
54         my $target = $r->construct_url($lurl) . '?' . $lurlvar . '=' . uri_escape($redirect);
55         $target =~ s/^http:/https:/o; # This should never be needed due to the redirect above, but better safe than sorry
56         $r->headers_out->set(Location => $target);
57         # Lets not cache this either, just in case.
58         $r->headers_out->set('Cache-Control' => 'no-cache, no-store, must-revalidate');
59         $r->headers_out->set(Pragma => 'no-cache');
60         $r->headers_out->set(Expires => 0);
61         return Apache2::Const::HTTP_MOVED_TEMPORARILY;
62     }
63
64     # Convert check OU from shortname, if needed
65     $checkou = _get_org_id($checkou);
66
67     # If we have no check OU at this point, use the user's home OU
68     $checkou ||= $user->home_ou;
69
70     my $failed = 0;
71
72     if ($userperm) {
73         my @permissions = split(/\s*[ ,]\s*/, $userperm);
74         $failed++ unless _verify_permission($auth_ses, $user, $checkou, \@permissions);
75     }
76     if (!$failed && $userstanding) {
77         $failed++ unless _verify_standing($user);
78     }
79     if (!$failed && $userou) {
80         $failed++ unless _verify_home_ou($user, $userou);
81     }
82
83     # If we failed one of the above checks they aren't allowed in
84     if ($failed > 0) {
85         if ($failurl) {
86             my $target = $r->construct_url($failurl);
87             $r->headers_out->set(Location => $target);
88             return Apache2::Const::HTTP_MOVED_TEMPORARILY;
89         } else {
90             return Apache2::Const::HTTP_FORBIDDEN;
91         }
92     }
93
94     # Forced referrer for some referrer auth services?
95     if ($referrersetting) {
96         my $referrervalue = _get_ou_setting($referrersetting, $checkou);
97         if ($referrervalue && $referrervalue->{value}) {
98             $r->headers_in->set('Referer', $referrervalue->{value});
99         }
100     }
101
102     # If we haven't thrown them out yet, let them through
103     return Apache2::Const::OK;
104 }
105
106 # "Private" functions
107
108 # Verify our login
109 sub _verify_login {
110     my ($token) = @_;
111     return undef unless $token;
112
113     my $user = OpenSRF::AppSession
114         ->create('open-ils.auth')
115         ->request('open-ils.auth.session.retrieve', $token)
116         ->gather(1);
117
118     if (ref($user) eq 'HASH' && $user->{ilsevent} == 1001) {
119         return undef;
120     }
121
122     return $user if ref($user);
123     return undef;
124 }
125
126 # OU Shortname to ID
127 sub _get_org_id {
128     my ($org_identifier) = @_;
129     # Does this look like a number?
130     if ($org_identifier !~ '^[0-9]+$') {
131         # Not a database id
132         if ($org_identifier) { 
133             # Look up org unit by shortname
134             my $org_unit = OpenSRF::AppSession
135                 ->create('open-ils.actor')
136                 ->request('open-ils.actor.org_unit.retrieve_by_shortname', $org_identifier)
137                 ->gather(1);
138             if ($org_unit && ref($org_unit) ne 'HASH') {
139                 # We appear to have an org unit! So return the ID.
140                 return $org_unit->id;
141             }
142         }
143     } else {
144         # Looks like a database ID, so leave it alone.
145         return $org_identifier;
146     }
147     # If we have reached this point, assume that we found no useful ID.
148     return undef;
149 }
150
151 # Verify home OU
152 sub _verify_home_ou {
153     my ($user, $home_ou) = @_;
154     my $org_tree = OpenSRF::AppSession
155         ->create('open-ils.actor')
156         ->request('open-ils.actor.org_tree.ancestors.retrieve', $user->home_ou)
157         ->gather(1);
158     if ($org_tree && ref($org_tree) ne 'HASH') {
159         my %user_orgs;
160         do {
161             $user_orgs{$org_tree->id} = 1;
162             if ($org_tree->children) {
163                 $org_tree = @{$org_tree->children}[0];
164             } else {
165                 $org_tree = undef;
166             }
167         } while ($org_tree);
168
169         my @home_ous = split(/\s*[ ,]\s*/,$home_ou);
170         for my $cur_ou (@home_ous) {
171             $cur_ou = _get_org_id($cur_ou);
172             if ($user_orgs{$cur_ou}) {
173                 return 1;
174             }
175         }
176     }
177     return 0;
178 }
179
180 # Verify permission
181 sub _verify_permission {
182     my ($token, $user, $org_unit, $permissions) = @_;
183
184     my $failures = OpenSRF::AppSession
185         ->create('open-ils.actor')
186         ->request('open-ils.actor.user.perm.check', $token, $user->id, $org_unit, $permissions)
187         ->gather(1);
188
189     return !scalar(@$failures);
190 }
191
192 # Verify standing
193 sub _verify_standing {
194     my ($user) = @_;
195
196     # If barred you are not in good standing
197     return 0 if $user->barred;
198     # If inactive you are also not in good standing
199     return 0 unless $user->active;
200
201     # Possible addition: Standing Penalty Checks?
202
203     return 1;
204 }
205
206 sub _get_ou_setting {
207     my ($setting, $org_unit) = @_;
208
209     my $value = OpenSRF::AppSession->create('open-ils.actor')
210         ->request('open-ils.actor.ou_setting.ancestor_default', $org_unit, $setting)
211         ->gather(1);
212
213     return $value;
214 }
215
216 1;