1# Utility functions for mod_webauth tests.
2#
3# Written by Roland Schemers
4# Rewritten as a module by Jon Robertson <jonrober@stanford.edu>
5# Copyright 2003, 2013, 2014
6#     The Board of Trustees of the Leland Stanford Junior University
7#
8# Permission is hereby granted, free of charge, to any person obtaining a copy
9# of this software and associated documentation files (the "Software"), to
10# deal in the Software without restriction, including without limitation the
11# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12# sell copies of the Software, and to permit persons to whom the Software is
13# furnished to do so, subject to the following conditions:
14#
15# The above copyright notice and this permission notice shall be included in
16# all copies or substantial portions of the Software.
17#
18# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
21# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24# IN THE SOFTWARE.
25
26package WebAuth::Tests;
27
28use 5.006;
29use strict;
30use warnings;
31
32use Carp;
33use CGI qw(:standard);
34use CGI::Cookie;
35use Data::Dumper;
36use Template;
37
38use Exporter qw(import);
39our @EXPORT_OK = qw(build_page run_test app_lifetime_test last_used_test);
40
41our $VERSION;
42
43# This version matches the version of WebAuth with which this module was
44# released, but with two digits for the minor and patch versions.
45BEGIN {
46    $VERSION = '4.0700';
47}
48
49#############################################################################
50# Internal tests
51#############################################################################
52
53# Creates an array of hash records for all of our regular tests, suited to be
54# passed into our template.
55sub _standard_tests {
56    my $webauth_token_creation   = $ENV{WEBAUTH_TOKEN_CREATION};
57    my $webauth_token_expiration = $ENV{WEBAUTH_TOKEN_EXPIRATION};
58    my $webauth_user             = $ENV{WEBAUTH_USER};
59    my $remote_user              = $ENV{REMOTE_USER};
60    my $auth_type                = $ENV{AUTH_TYPE};
61
62    my @standard_tests;
63
64    my $record = run_test('AUTH_TYPE',
65                          $auth_type eq 'WebAuth',
66                          'WebAuth',
67                          "not WebAuth, it's $auth_type",
68                          1);
69    push (@standard_tests, $record);
70
71    $record = run_test('REMOTE_USER',
72                       $remote_user ne '',
73                       $remote_user,
74                       'not set!',
75                       1);
76    push (@standard_tests, $record);
77
78    $record = run_test('WEBAUTH_USER',
79                       $webauth_user ne '',
80                       $webauth_user,
81                       'not set!',
82                       1);
83    push (@standard_tests, $record);
84
85    $record = run_test('WEBAUTH_USER == REMOTE_USER',
86                       $webauth_user eq $remote_user,
87                       'they are equal',
88                       'they are not equal!',
89                       0);
90    push (@standard_tests, $record);
91
92    $record = run_test('WEBAUTH_TOKEN_CREATION',
93                       $webauth_token_creation ne '',
94                       scalar(localtime($webauth_token_creation)),
95                       'not set!',
96                       1);
97    push (@standard_tests, $record);
98
99    $record = run_test('WEBAUTH_TOKEN_EXPIRATION',
100                       $webauth_token_expiration ne '',
101                       scalar(localtime($webauth_token_expiration)),
102                       'not set!',
103                       1);
104    push (@standard_tests, $record);
105
106    return \@standard_tests;
107}
108
109# Creates an array of hash records for all of our cookies, suited to be
110# passed into our template.
111sub _cookies {
112    my @cookies;
113    my %cookies = CGI::Cookie->fetch;
114    foreach my $var (sort(keys(%cookies))) {
115        next unless $var =~ /^webauth_/ && $var !~ /^webauth_wpt_/;
116        my ($name, $val) = split('=', $cookies{$var});
117
118        my %record = (name  => $var,
119                      value => _truncate_str($val, 40),
120        );
121        push (@cookies, \%record);
122    }
123
124    return \@cookies;
125}
126
127# Creates an array of hash records for directly relevant environment
128# variables, suited to be  passed into our template.
129sub _environment_important {
130    my @environment;
131    foreach my $var (sort(keys(%ENV))) {
132        next unless $var eq 'REMOTE_USER' || $var eq 'AUTH_TYPE'
133            || $var =~ m{^WEBAUTH_};
134        my %record = (name  => $var,
135                      value => _truncate_str($ENV{$var}, 80),
136        );
137        push (@environment, \%record);
138    }
139
140    return \@environment;
141}
142
143# Creates an array of hash records for less important environment variables,
144# suited to be passed into our template.
145sub _environment_misc {
146    my @environment;
147    foreach my $var (sort(keys(%ENV))) {
148        next if $var eq 'REMOTE_USER';
149        next if $var eq 'AUTH_TYPE';
150        next if $var =~ m{^WEBAUTH_};
151        my %record = (name  => $var,
152                      value => _truncate_str($ENV{$var}, 80),
153        );
154        push (@environment, \%record);
155    }
156
157    return \@environment;
158}
159
160# Creates an array of hash records for the multifactor configuration and
161# levels.
162sub _multifactor_tests {
163    my $webauth_factors_initial = $ENV{WEBAUTH_FACTORS_INITIAL};
164    my $webauth_factors_session = $ENV{WEBAUTH_FACTORS_SESSION};
165    my $webauth_loa             = $ENV{WEBAUTH_LOA};
166
167    my @tests;
168    my $record = run_test('WEBAUTH_FACTORS_INITIAL',
169                          $webauth_factors_initial ne '',
170                          $webauth_factors_initial,
171                          'not set!',
172                          1);
173    push (@tests, $record);
174
175    $record = run_test('WEBAUTH_FACTORS_SESSION',
176                       $webauth_factors_session ne '',
177                       $webauth_factors_session,
178                       'not set!',
179                       1);
180    push (@tests, $record);
181
182    $record = run_test('WEBAUTH_LOA',
183                       $webauth_loa ne '',
184                       $webauth_loa,
185                       'not set!',
186                       1);
187    push (@tests, $record);
188
189    return \@tests;
190}
191
192#############################################################################
193# Internal misc functions
194#############################################################################
195
196# Given a string and a maximum length for it, truncate it and append a message
197# after if the string exceeds the maximum length.  Return the string.
198sub _truncate_str {
199    my ($str, $max_length) = @_;
200    if (length($str) > $max_length) {
201        $str = substr($str, 0, $max_length) . '...(truncated)';
202    }
203    return $str;
204}
205
206#############################################################################
207# External tests
208#############################################################################
209
210# Given a test name, a result test to perform, and a good and bad comments to
211# use on pass/fail, return a hash for the test's status.
212sub run_test {
213    my ($test_name, $result, $good, $bad, $bold) = @_;
214
215    my %record;
216    $record{name}         = $test_name;
217    $record{result}       = $result;
218    $record{comment}      = $result ? $good : $bad;
219    $record{comment_bold} = $bold;
220    return \%record;
221}
222
223# Run all tests for App token lifetime and return as an array for reporting.
224sub app_lifetime_test {
225    my $webauth_token_expiration = $ENV{WEBAUTH_TOKEN_EXPIRATION};
226    my @tests;
227    my $record = run_test('WEBAUTH_TOKEN_EXPIRATION',
228                          $webauth_token_expiration < time()+10,
229                          'expires in less then 10 seconds',
230                          'does not expire in less then 10 seconds',
231                          0);
232    push (@tests, $record);
233    return \@tests;
234}
235
236# Test to make sure that the last used time is outside of a narrow window for
237# 'now'.
238sub last_used_test {
239    my $webauth_token_lastused = $ENV{WEBAUTH_TOKEN_LASTUSED};
240    my $time = time();
241    my $low  = $time - 10;
242    my $high = $time + 10;
243
244    my @tests;
245    my $record = run_test('WEBAUTH_TOKEN_LASTUSED',
246                          ($webauth_token_lastused > $low &&
247                           $webauth_token_lastused < $high),
248                          scalar(localtime($webauth_token_lastused)),
249                          'not within the acceptable time window!',
250                          1);
251    push (@tests, $record);
252    return \@tests;
253}
254
255#############################################################################
256# External misc functions
257#############################################################################
258
259# Takes a number of args for page title and any extra tests or unusual flags
260# to use.  Then grabs several standard tests and tosses them in to create a
261# test page result.
262sub build_page {
263    my ($args) = @_;
264
265    # Set all values to be passed to the template.
266    my %values = (test_num              => $args->{test_number},
267                  test_desc             => $args->{test_desc},
268                  extended_description  => $args->{extended_desc},
269                  standard_tests        => _standard_tests(),
270                  cookies               => _cookies(),
271                  environment_important => _environment_important(),
272                  environment_misc      => _environment_misc(),
273                  extra_tests_title     => $args->{extra_title},
274                  extra_tests           => $args->{extra_tests},
275                  logout                => $args->{logout} || '/tests/logout',
276                  remote_user           => $ENV{REMOTE_USER},
277                  unauth_location       => $args->{unauth_loc} || 0,
278    );
279
280    # Multifactor tests have those as their test type.
281    if ($args->{multifactor}) {
282        $values{extra_tests_title} = 'Performing Multifactor tests';
283        $values{extra_tests}       = _multifactor_tests();
284    }
285
286    # Build and return the page from a template.
287    my $tt = Template->new (RELATIVE => 1) or carp ("$Template::ERROR\n");
288    my $output = '';
289    my $fname  = $args->{template} || '../test.tt2';
290    $tt->process ($fname, \%values, \$output)
291        or carp ($tt->error."\n");
292    return $output;
293}
294
2951;
296
297__END__
298
299##############################################################################
300# Documentation
301##############################################################################
302
303=for stopwords
304WebAuth WebLogin multifactor
305
306=head1 NAME
307
308WebAuth::Tests - Assists with constructing WebAuth Apache module tests
309
310=head1 SYNOPSIS
311
312    use WebAuth::Tests qw(build_page);
313
314    my %settings = (
315        test_number   => 1,
316        test_desc     => 'basic WebAuth test',
317        extended_desc => \@extended,
318    );
319
320    print "Content-type: text/html\n\n";
321    print build_page(\%settings);
322
323=head1 DESCRIPTION
324
325This module provides shared code for the test suite for the mod_webauth
326Apache module.  It is used by the individual test programs and test Apache
327configuration to construct a variety of scenarios to exercise most of the
328functionality of mod_webauth and some of the WebLogin and mod_webkdc
329features.  Most of the test setup is in the Apache configuration, but each
330test corresponds to a Perl script, which uses this module, that checks
331environment variables to see that the tests did what they should and
332provides useful debugging information if they did not.
333
334This module is primarily intended for use with the tests that are included
335with WebAuth and currently includes some defaults that make it difficult
336to use for other purposes.  The goal is to eventually make it more general
337so that it can be used for building additional tests local to a particular
338site.
339
340=head1 FUNCTIONS
341
342None of the following functions are exported by default.  They must be
343explicitly requested when using the WebAuth::Tests module.
344
345=over 4
346
347=item build_page(SETTINGS)
348
349Performs the work of building a test page that shows the test number,
350title, information about the test run, and various tables showing current
351status and tests run.  SETTINGS should be a reference to a hash, which may
352contain one or more of the following settings:
353
354=over 4
355
356=item test_number
357
358The number of this test, passed to the template.
359
360=item test_desc
361
362The short description of this test, passed to the template.
363
364=item extended_description
365
366The extended description of this test.  This should be a reference to an
367array that contains one or more paragraphs of text as strings.  Each
368element of the array will be wrapped in <p> tags.
369
370=item extra_tests
371
372An anonymous array of hash references, each of which represents a test
373result.  The hash should have three keys: C<name>, C<result>, and
374C<comment>.  C<name> should be the name of this test, C<result> should
375be either C<PASS> or C<FAIL>, and C<comment> should provide additional
376information about the test.
377
378=item extra_tests_title
379
380If there are extra tests, this will be used as the heading for that test
381output.  If this setting is present, extra_tests should also be present.
382
383=item multifactor
384
385Set this to true to also perform multifactor tests.
386
387=item template
388
389The path (possibly relative) to the template used for generating HTML.
390By default, this is set to F<../test.tt2>, which works for the default
391mod_webauth test suite (and probably not for anything else).
392
393=item unauth_loc
394
395Set this to true if this test will be running without authentication.
396This is used by the template to change some of the boilerplate text.
397
398=back
399
400=item run_test(NAME, RESULT, GOOD, BAD, BOLD)
401
402Performs a test of WebAuth information, returning an anonymous hash
403showing the results of the test in the format required by the
404C<extra_tests> setting to build_page().  It takes the name of the test, a
405boolean value that represents the result of the test, text to display on
406true or false results, and a flag indicating whether to bold true results.
407
408=item app_lifetime_test
409
410Creates an anonymous array of hash references that contains the results of
411a test for the lifetime of an application token.  The result is suitable
412for inclusion in C<extra_tests>.
413
414=item last_used_test()
415
416Creates an anonymous array of hash references that contains the results of
417a test for current last-used time for a token.  The result is suitable for
418inclusion in C<extra_tests>.
419
420=back
421
422=head1 BUGS
423
424The interactions between this module and its template aren't currently
425completely documented.
426
427No one has yet used this module for anything other than the mod_webauth
428test suite included in the distribution.  It will probably need work to
429be usable for writing site-specific tests.
430
431=head1 AUTHOR
432
433Roland Schemers and Jon Robertson <jonrober@stanford.edu>.
434
435=head1 SEE ALSO
436
437Template(3)
438
439This module is part of WebAuth.  The current version is available from
440L<http://webauth.stanford.edu/>.
441
442=cut
443