#!/usr/bin/env perl # # Version: $Id$ # use strict; use warnings; use CGI; use CGI::Carp qw( fatalsToBrowser); use URI; # # constants # my @ATTRIBUTES_REQUIRED = qw( eduPersonPrincipalName:eppn eduPersonTargetedID:persistent_id ); my @ATTRIBUTES_OPTIONAL = qw( eduPersonScopedAffiliation:affiliation:eduPersonAffiliation cn displayName ); # allow override from environment ... if (exists $ENV{'SHIBTEST_ATTRIBUTES_REQUIRED'}) { @ATTRIBUTES_REQUIRED = split('\s+', $ENV{'SHIBTEST_ATTRIBUTES_REQUIRED'}); } if (exists $ENV{'SHIBTEST_ATTRIBUTES_OPTIONAL'}) { @ATTRIBUTES_OPTIONAL = split('\s+', $ENV{'SHIBTEST_ATTRIBUTES_OPTIONAL'}); } # # code below ... nothing to change there ... # sub xml_escape { my $s = shift; $s =~ s!&!&!gs; $s =~ s!!>!gs; return $s; } sub render_table_rows { my $caption = shift; my $keys = shift; my $i = 0; print '', '', $caption, '', ''; if (scalar(@{$keys}) > 0) { foreach my $key (@{$keys}) { my $value = $ENV{$key}; $value =~ s!\n*!!gs; $value =~ s!\s*(;|\$)\s*!\n!gs; $value = xml_escape($value); $value =~ s!\n!
!gs; print ''; print '', $key, '', '', $value, '', ''; } } else { print '', '[NONE]'; } } sub dump_shibboleth_attributes { my $debug_env = shift; my @keys = sort(keys(%ENV)); my @attrs = grep(!m/^(HTTPS|SERVER_|SCRIPT_|PATH|QUERY_STRING|GATEWAY|DOCUMENT_ROOT|REMOTE|REQUEST|HTTP_|AUTH_TYPE|Shib_)/i, @keys); my @shib = grep(m/Shib_/i, @keys); render_table_rows('Shibboleth Attributes:', \@attrs); render_table_rows('Shibboleth Enviroment Variables:', \@shib); if (defined ($debug_env)) { render_table_rows('All Environment Variables:', \@keys); } } sub dump_shibboleth_assertions { my $count = shift; return unless defined($count) && $count > 0; # try to load LWP and XML::Twig and bail if not available ... eval { require LWP; require XML::Twig; }; if ($@) { return; } print '', '', 'Raw SAML Assertion(s)', '', ''; my $j = 0; my $browser = LWP::UserAgent->new; ASSERTION: for (my $i = 1; $i <= $count; $i++) { my $url = $ENV{sprintf('Shib_Assertion_%02d', $i)}; next ASSERTION unless defined ($url); print ''; print 'Assertion ', $i, ''; my $response = $browser->get($url); if ($response->is_success) { my $twig = XML::Twig->new(pretty_print => 'indented', output_encoding => 'utf-8', no_prolog => '1', keep_original_prefix => '1'); $twig->parse($response->content); my $s = $twig->sprint(); $s = xml_escape($s); $s =~ s! ! !gs; $s =~ s!\n!
!gs; print '', $s, ''; } else { print '', 'Cannot retieve assertion: ', xml_escape($response->status_line), '', ''; } print ''; } } sub make_self_uri { my $scheme = exists $ENV{'HTTPS'} ? 'https' : 'http'; my $uri = URI->new($scheme . '://' . $ENV{'SERVER_NAME'}); $uri->path($ENV{'REQUEST_URI'}); return $uri->as_string(); } sub make_shibboleth_uri { my $path = shift; # XXX: always assume https for Shibboleth URIs ... my $uri = URI->new('https://' . $ENV{'SERVER_NAME'}); $uri->path($path); return $uri; } sub make_login_uri { my $uri = $ENV{'SHIBTEST_LOGIN_URI'}; if (defined($uri)) { $uri = URI->new($uri); } else { $uri = make_shibboleth_uri('/Shibboleth.sso/Login'); } $uri->query_form({ target => make_self_uri(), }); return $uri->as_string(); } sub make_logout_uri { my $uri = $ENV{'SHIBTEST_LOGOUT_URI'}; if (defined($uri)) { $uri = URI->new($uri); } else { $uri = make_shibboleth_uri('/Shibboleth.sso/Logout'); } $uri->query_form({ return => make_self_uri(), }); return $uri->as_string(); } sub scan_attributes { my $scan_ref = shift; my $optional = shift; my $missing = 0; foreach my $aliases (@{$scan_ref}) { my $found = undef; my @attrs = split(':', $aliases); KEY: foreach my $b (keys(%ENV)) { foreach my $a (@attrs) { if (lc($a) eq lc($b)) { $found = $b; last KEY; } } } if (defined($found)) { print '

', ($optional ? 'Optional' : 'Required'), ' attribute ', $attrs[0], ' is available', ($found ne $attrs[0] ? " (exported as $found)" : ''), '.

'; } else { print '', ($optional ? '

Optional' : '

Required'), ' attribute ', $attrs[0], ' is not available.

'; $missing++; } } return $missing; } sub main { my $q = shift; if (defined($ENV{'Shib_Session_ID'})) { # logout link my $idp = $ENV{'Shib_Identity_Provider'}; if (!defined($idp)) { $idp = '[UNKNOWN]'; } print '

'; print 'A Shibboleth session was established with ', $idp, '.'; if (defined($ENV{'SHIBTEST_LAZY'})) { print ' [Logout]
'; print 'NB: if this webserver is configured to always requires ', 'authentication for this page, you will be immediately ', 'redirected to the WAYF/Discovery service after logging out!'; } print '

'; my $errors = 0; my $warnings = 0; # CLARIN required attributes if (scalar(@ATTRIBUTES_REQUIRED) > 0) { $errors += scan_attributes(\@ATTRIBUTES_REQUIRED, 0); } # CLARIN optional attributes if (scalar(@ATTRIBUTES_OPTIONAL) > 0) { $warnings += scan_attributes(\@ATTRIBUTES_OPTIONAL, 1); } # remote user my $user = $ENV{'REMOTE_USER'}; $warnings++ unless defined($user); print '

'; print 'REMOTE_USER: ', (defined($user) ? $user : 'N/A (not exported by mod_shib?!)'); print '

'; if ($errors == 0) { print '

Interoperability between your SP ', 'and the IDP is ', ($warnings > 0 ? 'sufficent' : 'optimal'), '. ', $errors, ' error(s), ', $warnings, ' warning(s)

'; } else { print '

Interoperability between your SP ', 'and the IDP is problematic! ', $errors, ' error(s), ', $warnings, ' warning(s)
', 'Please check SP config and IDP release policy.

'; } # attribute / environment variable / assertion print ''; my $debug_env = (defined($q) && $q->param('debug_env')); dump_shibboleth_attributes($debug_env); dump_shibboleth_assertions($ENV{'Shib_Assertion_Count'}); print '
'; } else { # login link print '

No Shibboleth session exists, please Login.

'; } } my $style = <new(); print $q->header(-type => 'text/html', -charset => 'utf-8'); print $q->start_html(-title => 'CLARIN SPF Interoperability Test Page', -style => { -code => $style }); print $q->h1('CLARIN SPF Interoperability Test Page'); main($q); print $q->end_html; exit 0;