File Coverage

blib/lib/Apache2/AuthAny/RequestConfig.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package Apache2::AuthAny::RequestConfig;
2              
3 1     1   1397 use strict;
  1         3  
  1         31  
4 1     1   364 use Apache2::Module ();
  0            
  0            
5             use Apache2::Access ();
6             use Apache2::Request ();
7             use URI::Escape;
8             use Digest::MD5 qw(md5_hex);
9             use MIME::Base64;
10              
11             use Apache2::Const -compile => qw(OK DECLINED REDIRECT HTTP_UNAUTHORIZED);
12             use Data::Dumper("Dumper");
13             use CGI;
14             use CGI::Cookie;
15             use Apache2::AuthAny::Cookie ();
16             use Apache2::AuthAny::DB ();
17             use Apache2::AuthAny::AuthUtil ();
18             our $aaDB;
19             our $VERSION = '0.201';
20              
21             my @system_skip_auth = qw(/Shibboleth);
22              
23             sub handler {
24             my $r = shift;
25              
26             my $cf = Apache2::Module::get_config('Apache2::AuthAny',
27             $r->server,
28             $r->per_dir_config) || {};
29              
30             my $uri = $r->uri;
31             my $user_gate = $cf->{AuthAnyGateURL} || '';
32             my $gate_dir = $user_gate;
33             $gate_dir =~ s{/[^/]*$}{};
34              
35             if ($uri eq $user_gate || ($gate_dir && $uri =~ m{^$gate_dir}) ) {
36             # Prevent any authentication attempt on the gate page.
37             $r->log->info("RequestConfig: On gate page, '$uri'");
38             $r->set_handlers(PerlAuthenHandler => "sub {Apache2::Const::OK}");
39             $r->set_handlers(PerlAuthzHandler => "sub {Apache2::Const::OK}");
40             } elsif ($uri =~ m{/aa_auth/(.*?)/}) {
41             my $provider_string = $1;
42             my ($auth_provider, $logout_key) = split("_aa-key_", $provider_string);
43             $r->log->info("Apache2::AuthAny::RequestConfig: Authenticating with '$auth_provider'");
44              
45             if (lc($r->auth_type) eq 'auth-any') {
46             # This auth provider does not use the Authen/Authz phases. To prevent
47             # errors from DocumentRoot level Require directives, disable the
48             # Authen/Authz phases
49             $r->set_handlers(PerlAuthenHandler => "sub {Apache2::Const::OK}");
50             $r->set_handlers(PerlAuthzHandler => "sub {Apache2::Const::OK}");
51             }
52              
53             my $pid = Apache2::AuthAny::Cookie::pid($r);
54             $r->pnotes(pid => $pid);
55              
56             if ($auth_provider ne 'google') { # Google auth using PHP
57             $r->handler('perl-script');
58             $r->set_handlers(PerlResponseHandler => 'Apache2::AuthAny::Cookie::post_login');
59             }
60              
61             if (lc($r->auth_type) eq 'basic') {
62             # The AuthName randomizer is needed for IE to keep it
63             # from skipping the challenge when a known AuthName is sent.
64             my $auth_name = $r->auth_name() || 'Private';
65             my $rand_int = int(100000 * (1 + rand(4)));
66             $r->auth_name($auth_name . $rand_int);
67              
68             # Make sure the auth request is going to the current directory
69             if ($logout_key ne $pid->{logoutKey}) {
70             Apache2::AuthAny::AuthUtil::goToGATE($r, 'tech', {msg => "mismatching logout keys."})
71             }
72              
73             # After successful authentication, set a new logoutKey
74             $r->set_handlers(PerlFixupHandler => 'Apache2::AuthAny::FixupHandler::update_logout_key');
75              
76             # Go to meta redirect to GATE instead of showing ugly browser message
77             # if user chooses "Cancel" on challenge popup.
78             my $req = Apache2::Request->new($r);
79             my $request = $req->param('req');
80             my $custom_response = <<"RESPONSE";
81            
82            
83            
84            
85            
86            
87            
88            
89             RESPONSE
90             $r->custom_response(Apache2::Const::HTTP_UNAUTHORIZED, $custom_response);
91             $r->log->info("Apache2::AuthAny::RequestConfig: Basic custom_response set");
92             }
93              
94             } elsif (lc($r->auth_type) eq 'auth-any') {
95             $aaDB = Apache2::AuthAny::DB->new() unless $aaDB;
96              
97             my $pid;
98             my $scripted_pid = get_scripted_pid($r, $cf);
99              
100             # First, check for scripted access by looking in "Authorization" header
101             if ($scripted_pid) {
102             $pid = $scripted_pid;
103             } else {
104             $pid = Apache2::AuthAny::Cookie::pid($r);
105             }
106             $r->pnotes(pid => $pid);
107              
108             my $req = Apache2::Request->new($r);
109             if (defined $req->param('aalogout') ) {
110             return Apache2::AuthAny::AuthUtil::logout($r, $pid);
111             }
112              
113             if (defined $req->param('aalogin') ) {
114             return Apache2::AuthAny::AuthUtil::goToGATE($r, 'first_access');
115             }
116              
117             my $skip_patterns = $cf->{AuthAnySkipAuthentication} || [];
118             push @$skip_patterns, @system_skip_auth;
119             my @matching_patterns = grep {$r->uri =~ m!$_!} @$skip_patterns;
120             if (@matching_patterns) {
121             $r->set_handlers(PerlAuthenHandler => "sub {Apache2::Const::OK}");
122             $r->set_handlers(PerlAuthzHandler => "sub {Apache2::Const::OK}");
123             } else {
124             $r->set_handlers(PerlAuthenHandler => 'Apache2::AuthAny::AuthenHandler');
125             $r->set_handlers(PerlAuthzHandler => 'Apache2::AuthAny::AuthzHandler');
126             }
127             # If we make it through authen and authz, update the last access
128             $r->set_handlers(PerlFixupHandler => 'Apache2::AuthAny::FixupHandler');
129             set_env($r, $pid, $cf);
130             }
131             return Apache2::Const::DECLINED;
132             }
133              
134             sub set_env {
135             my ($r, $pid, $cf) = @_;
136              
137             my ($authId, $authProvider);
138             unless ($pid->{state} eq 'logged_out') {
139             ($authId, $authProvider) = ($pid->{authId}, $pid->{authProvider});
140             }
141              
142             if ($pid->{scripted}) {
143             $r->subprocess_env('AA_SCRIPTED' => 1);
144             }
145              
146             if ($authId && $pid->{SID}) {
147             # login occurred in this browser session
148             $r->subprocess_env('AA_SESSION' => 1);
149             }
150              
151             # resolve identity if possible
152             my $identifiedUser = $aaDB->getUserByAuthIdAndProvider($authId, $authProvider) || {};
153             my $user;
154             if ($identifiedUser->{username}) {
155             $user = $identifiedUser->{username};
156              
157             my $roles = $aaDB->getUserRoles($identifiedUser->{UID});
158             $r->subprocess_env(AA_ROLES => join(",", @$roles));
159              
160             # role choices are never used in Require directives
161             my %user_role_choice;
162             my $role_choices = $aaDB->getUserRoleChoices($identifiedUser->{UID});
163             foreach my $role (@$role_choices) {
164             $user_role_choice{$role} = 1;
165             }
166             my @roles_active = grep { $user_role_choice{$_} } @$roles;
167             $r->subprocess_env(AA_ROLES_ACTIVE => join(",", @roles_active));
168              
169             my $identities = $aaDB->getUserIdentities($identifiedUser->{UID});
170             my @idents = map {"$_->{authId}|$_->{authProvider}"} @$identities;
171             $r->subprocess_env(AA_IDENTITIES => join(",", @idents));
172              
173             foreach my $field (keys %$identifiedUser) {
174             $r->subprocess_env("AA_IDENT_$field" => $identifiedUser->{$field});
175             }
176             } elsif ($authId && $authProvider) {
177             $user = "$authId|$authProvider";
178             }
179              
180             $r->user($user) if $user;
181             $r->subprocess_env(REMOTE_USER => $user);
182              
183             $r->subprocess_env(AA_USER => $authId);
184             $r->subprocess_env(AA_PROVIDER => $authProvider);
185              
186             # Timeout
187             my $timeout = 155520000; # defaults to 5 years
188             if (defined $identifiedUser->{timeout}) {
189             $timeout = $identifiedUser->{timeout};
190             } elsif (defined $cf->{AuthAnyTimeout}) {
191             $timeout = $cf->{AuthAnyTimeout};
192             }
193              
194             if ($pid->{state} eq 'authenticated' && time() - $pid->{last} < $timeout) {
195             $r->subprocess_env(AA_TIMEOUT => $timeout);
196             } elsif ($authId ) {
197             $aaDB->statePCookie($pid, 'recognized');
198             } else {
199             $aaDB->statePCookie($pid, 'logged_out');
200             }
201              
202             $r->subprocess_env(AA_STATE => $pid->{state});
203             # Passing gate for logout convienience
204             $r->subprocess_env();
205             }
206              
207             sub get_scripted_pid {
208             my $r = shift;
209             my $cf = shift;
210             if ($cf->{AuthAnyBasicAuthUserFile}) {
211             unless (open(HTPASSWD, $cf->{AuthAnyBasicAuthUserFile})) {
212             my $msg = "Cannot read '$cf->{AuthAnyBasicAuthUserFile}' $!";
213             die $msg;
214             }
215              
216             my ($http_user, $http_password) = get_user_and_password($r);
217             if ($http_user && $http_password) {
218              
219             my $stored_passwd;
220             while () {
221             chomp;
222             my ($username, $crypt_passwd) = split(":", $_, 2);
223             if ($username eq $http_user) {
224             if (crypt($http_password, $crypt_passwd) eq $crypt_passwd) {
225             $r->log->info("RequestConfig: From HTTP header: $username");
226             return {PID => 'unused',
227             SID => 'unused',
228             logoutKey => 'unused',
229             state => 'authenticated',
230             scripted => 1,
231             authId => $username,
232             authProvider => 'basic',
233             last => 2298416724, # time in the future
234             };
235             } else {
236             my $msg = "RequestConfig: Basic user found in " .
237             "$cf->{AuthAnyBasicAuthUserFile}, however password is incorrect";
238             $r->log->warn($msg);
239             last;
240             }
241             }
242             }
243             }
244             }
245             }
246              
247             sub get_user_and_password {
248             my $r = shift;
249             my $Authorization = $r->headers_in->{Authorization};
250             if ($Authorization) {
251             my ($type, $hash) = split " ", $Authorization;
252             my $u_and_p = decode_base64($hash);
253             if ($u_and_p) {
254             my ($user, $password) = split(/:/, $u_and_p, 2);
255             return ($user, $password);
256             }
257             }
258             return undef;
259             }
260              
261             1;