| 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; |