source: aai/shib_test/shib_test.pl @ 5723

Last change on this file since 5723 was 5723, checked in by Oliver Schonefeld, 10 years ago
  • make up mind and 'shib_test.pl' to own sub-directory
  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 8.9 KB
Line 
1#!/usr/bin/env perl
2#
3# Version: $Id: shib_test.pl 5723 2014-10-16 09:29:47Z schonefeld@ids-mannheim.de $
4#
5use strict;
6use warnings;
7use CGI;
8use CGI::Carp qw( fatalsToBrowser);
9use URI;
10
11#
12# constants
13#
14my @ATTRIBUTES_REQUIRED = qw(
15    eduPersonPrincipalName:eppn
16    eduPersonTargetedID:persistent_id
17);
18my @ATTRIBUTES_OPTIONAL = qw(
19    eduPersonScopedAffiliation:affiliation:eduPersonAffiliation
20    cn
21    displayName
22);
23
24
25# allow override from environment ...
26if (exists $ENV{'SHIBTEST_ATTRIBUTES_REQUIRED'}) {
27    @ATTRIBUTES_REQUIRED = split('\s+', $ENV{'SHIBTEST_ATTRIBUTES_REQUIRED'});
28}
29if (exists $ENV{'SHIBTEST_ATTRIBUTES_OPTIONAL'}) {
30    @ATTRIBUTES_OPTIONAL = split('\s+', $ENV{'SHIBTEST_ATTRIBUTES_OPTIONAL'});
31}
32
33
34#
35# code below ... nothing to change there ...
36#
37sub xml_escape {
38    my $s = shift;
39
40    $s =~ s!&!&!gs;
41    $s =~ s!<!&lt;!gs;
42    $s =~ s!>!&gt;!gs;
43    return $s;
44}
45
46
47sub render_table_rows {
48    my $caption = shift;
49    my $keys    = shift;
50    my $i = 0;
51    print '<tr class="header">', '<th colspan="2">',
52        $caption, '</th>', '</tr>';
53    if (scalar(@{$keys}) > 0) {
54        foreach my $key (@{$keys}) {
55            my $value = $ENV{$key};
56            $value =~ s!\n*!!gs;
57            $value =~ s!\s*(;|\$)\s*!\n!gs;
58            $value = xml_escape($value);
59            $value =~ s!\n!<br />!gs;
60            print '<tr class="', ($i++ % 2 == 0 ? 'even' : 'odd'), '">';
61            print '<td>', $key, '</td>', '<td>', $value, '</td>', '</tr>';
62        }
63    }
64    else {
65        print '<tr class="even"><td colspan="2">',
66            '<span class="error center">[NONE]</span></tr>';
67    }
68}
69
70
71sub dump_shibboleth_attributes {
72    my $debug_env = shift;
73   
74    my @keys = sort(keys(%ENV));
75    my @attrs = grep(!m/^(HTTPS|SERVER_|SCRIPT_|PATH|QUERY_STRING|GATEWAY|DOCUMENT_ROOT|REMOTE|REQUEST|HTTP_|AUTH_TYPE|Shib_)/i, @keys);
76    my @shib = grep(m/Shib_/i, @keys);
77
78    render_table_rows('Shibboleth Attributes:', \@attrs);
79    render_table_rows('Shibboleth Enviroment Variables:', \@shib);
80    if (defined ($debug_env)) {
81        render_table_rows('All Environment Variables:', \@keys);
82    }
83}
84
85
86sub dump_shibboleth_assertions {
87    my $count = shift;
88
89    return unless defined($count) && $count > 0;
90
91    # try to load LWP and XML::Twig and bail if not available ...
92    eval {
93        require LWP;
94        require XML::Twig;
95    };
96    if ($@) {
97        return;
98    }
99    print '<tr class="header">', '<th colspan="2">',
100        'Raw SAML Assertion(s)', '</th>', '</tr>';
101    my $j = 0;
102    my $browser = LWP::UserAgent->new;
103    ASSERTION:
104    for (my $i = 1; $i <= $count; $i++) {
105        my $url = $ENV{sprintf('Shib_Assertion_%02d', $i)};
106        next ASSERTION unless defined ($url);
107
108        print '<tr class="', ($j++ % 2 == 0 ? 'even' : 'odd'), '">';
109        print '<td>Assertion ', $i, '</td>';
110        my $response = $browser->get($url);
111        if ($response->is_success) {
112            my $twig = XML::Twig->new(pretty_print => 'indented',
113                                      output_encoding => 'utf-8',
114                                      no_prolog => '1',
115                                      keep_original_prefix => '1');
116            $twig->parse($response->content);
117            my $s = $twig->sprint();
118            $s = xml_escape($s);
119            $s =~ s! !&nbsp;!gs;
120            $s =~ s!\n!<br />!gs;
121            print '<td>', $s, '</td>';
122        }
123        else {
124            print '<td>', '<span class="error">Cannot retieve assertion: ',
125            xml_escape($response->status_line), '</span>', '</td>';
126        }
127        print '</tr>';
128    }
129}
130
131
132sub make_self_uri {
133    my $scheme = exists $ENV{'HTTPS'} ? 'https' : 'http';
134    my $uri = URI->new($scheme . '://' . $ENV{'SERVER_NAME'});
135    $uri->path($ENV{'REQUEST_URI'});
136    return $uri->as_string();
137}
138
139
140sub make_shibboleth_uri {
141    my $path = shift;
142
143    # XXX: always assume https for Shibboleth URIs ...
144    my $uri = URI->new('https://' . $ENV{'SERVER_NAME'});
145    $uri->path($path);
146    return $uri;
147}
148
149
150sub make_login_uri {
151    my $uri = $ENV{'SHIBTEST_LOGIN_URI'};
152    if (defined($uri)) {
153        $uri = URI->new($uri);
154    }
155    else {
156        $uri = make_shibboleth_uri('/Shibboleth.sso/Login');
157    }
158    $uri->query_form({
159        target => make_self_uri(),
160    });
161    return $uri->as_string();
162}
163
164
165sub make_logout_uri {
166    my $uri = $ENV{'SHIBTEST_LOGOUT_URI'};
167    if (defined($uri)) {
168        $uri = URI->new($uri);
169    }
170    else {
171        $uri = make_shibboleth_uri('/Shibboleth.sso/Logout');
172    }
173    $uri->query_form({
174        return => make_self_uri(),
175    });
176    return $uri->as_string();
177}
178
179
180sub scan_attributes {
181    my $scan_ref    = shift;
182    my $optional    = shift;
183    my $missing     = 0;
184
185    foreach my $aliases (@{$scan_ref}) {
186        my $found = undef;
187        my @attrs = split(':', $aliases);
188
189        KEY:
190        foreach my $b (keys(%ENV)) {
191            foreach my $a (@attrs) {
192                if (lc($a) eq lc($b)) {
193                    $found = $b;
194                    last KEY;
195                }
196            }
197        }
198
199        if (defined($found)) {
200            print '<p class="attr ok">',
201                ($optional ? 'Optional'
202                           : 'Required'),
203                ' attribute <code>', $attrs[0], '</code> is available',
204                ($found ne $attrs[0] ? " (exported as <code>$found</code>)"
205                                     : ''),
206                '.</p>';
207        }
208        else {
209            print '', ($optional ? '<p class="attr warn">Optional'
210                                 : '<p class="attr error">Required'),
211                ' attribute <code>', $attrs[0],
212                '</code> is not available.</p>';
213            $missing++;
214        }
215    }
216    return $missing;
217}
218
219
220sub main {
221    my $q = shift;
222
223    if (defined($ENV{'Shib_Session_ID'})) {
224        # logout link
225        my $idp = $ENV{'Shib_Identity_Provider'};
226        if (!defined($idp)) {
227            $idp = '<span class="error">[UNKNOWN]</span>';
228        }
229        print '<p>';
230        print 'A Shibboleth session was established with <em>', $idp,
231            '</em>.';
232        if (defined($ENV{'SHIBTEST_LAZY'})) {
233            print ' [<a href="', make_logout_uri(), '">Logout</a>]<br />';
234            print 'NB: if this webserver is configured to always requires ',
235                'authentication for this page, you will be immediately ',
236                'redirected to the WAYF/Discovery service after logging out!';
237        }
238        print '</p>';
239
240        my $errors   = 0;
241        my $warnings = 0;
242        # CLARIN required attributes
243        if (scalar(@ATTRIBUTES_REQUIRED) > 0) {
244            $errors += scan_attributes(\@ATTRIBUTES_REQUIRED, 0);
245        }
246
247        # CLARIN optional attributes
248        if (scalar(@ATTRIBUTES_OPTIONAL) > 0) {
249            $warnings += scan_attributes(\@ATTRIBUTES_OPTIONAL, 1);
250        }
251
252        # remote user
253        my $user = $ENV{'REMOTE_USER'};
254        $warnings++ unless defined($user);
255        print '<p class="attr ', (defined($user) ? 'ok' : 'warn'), '">';
256        print 'REMOTE_USER: ',
257            (defined($user) ? $user : 'N/A (not exported by mod_shib?!)');
258        print '</p>';
259
260        if ($errors == 0) {
261            print '<p class="ok result">Interoperability between your SP ',
262                'and the IDP is ',
263                ($warnings > 0 ? 'sufficent' : 'optimal'), '. ',
264                $errors, ' error(s), ',
265                $warnings, ' warning(s)</p>';
266        }
267        else {
268            print '<p class="error result">Interoperability between your SP ',
269                'and the IDP is problematic! ', $errors, ' error(s), ',
270                $warnings, ' warning(s) <br/>',
271                'Please check SP config and IDP release policy.</p>';
272        }
273        # attribute / environment variable / assertion
274        print '<table class="attr">';
275        my $debug_env = (defined($q) && $q->param('debug_env'));
276        dump_shibboleth_attributes($debug_env);
277        dump_shibboleth_assertions($ENV{'Shib_Assertion_Count'});
278        print '</table>';
279    }
280    else {
281        # login link
282        print '<p>No Shibboleth session exists, please <a href="',
283            make_login_uri(), '">Login</a>.</p>';
284    }
285}
286
287
288my $style = <<STYLE;
289body {
290    font-family: Arial, Verdana, sans-serif;
291    font-size: 12pt;
292    margin: 0;
293    padding: 2px;
294}
295
296h1 {
297    font-size: 150%;
298    margin: 0 0 5px 0;
299    padding: 0;
300}
301
302h2 {
303    font-size: 100%;
304    margin: 1px 0;
305    padding: 0;
306}
307
308p {
309    margin: 10px 0;
310    padding: 4px;
311}
312
313p.ok {
314    color: #FFFFFF;
315    background-color: #009900;
316}
317
318
319p.warn {
320    color: #000000;
321    background-color: #FFFF00;
322}
323
324p.error {
325    color: #FFFFFF;
326    background-color: #CC0000;
327    font-weight: bold;
328}
329
330p.attr {
331    margin: 1px 0;
332}
333
334p.result {
335    margin: 20px 0;
336    font-size: 120%;
337    font-weight: bold;
338    border: 2px solid #000000;
339}
340
341code {
342    font-family: "Courier New", monospace;
343    font-weight: bold;
344}
345
346span.error {
347    color: #CC0000;
348    background-color: inherit;
349    font-weight: bold;
350}
351
352table {
353    border: 1px solid #000000;
354    border-collapse: collapse;
355    margin: 0;
356    padding: 0;
357}
358
359td, th {
360    border: 1px solid #000000;
361    vertical-align: top;
362    text-align: left;
363    margin: 0;
364    padding: 4px;
365}
366
367th {
368    font-weight: bold;
369    font-size: 110%;
370    color: #FFFFFF;
371    background-color: transparent;
372}
373
374.header {
375    color: inherit;
376    background-color: #707677;
377}
378
379.even {
380    color: inherit;
381    background-color: #E7E7E7;
382}
383
384.odd {
385    color: inherit;
386    background-color: #CFCFCF;
387}
388STYLE
389
390my $q = CGI->new();
391print $q->header(-type => 'text/html', -charset => 'utf-8');
392print $q->start_html(-title => 'CLARIN SPF Interoperability Test Page',
393                     -style => { -code => $style });
394print $q->h1('CLARIN SPF Interoperability Test Page');
395main($q);
396print $q->end_html;
397exit 0;
Note: See TracBrowser for help on using the repository browser.