Newer
Older
#!/usr/bin/perl
## 15/09/2014, Olivier Salaün
## Web interface for the eduGAIN Test IdP Account Manager
renater.salaun
committed
use strict vars;
use utf8;
use lib "/opt/testidp/IdPAccountManager/lib";
use lib "/opt/testidp/IdPAccountManager/conf";
use CGI;
use CGI::Cookie;
use CGI::Util;
use Template;
use Template::Constants qw( :debug );
renater.salaun
committed
use POSIX;
use IdPAccountManager::TestAccount;
renater.salaun
committed
use IdPAccountManager::SAMLMetadata;
use IdPAccountManager::ServiceProvider;
use IdPAccountManager::AuthenticationToken;
## Defining parameters format
my $urn_or_url_regex = '(http(s?):\/\/|urn:)[^\\\$\*\"\'\`\^\|\<\>\n\s]+'; ## Format de type URL HTTP ou URN
my $url_regex = 'http(s?):\/\/[^\\\$\*\"\'\`\^\|\<\>\n\s]+';
my $email_regex = '([\w\-\_\.\/\+\=\'\&]+|\".*\")\@[\w\-]+(\.[\w\-]+)+';
my $domains_regex = '[\w\.\-]+(,[\w\.\-]+)*';
my %format = (
## URL
#'attributeauthority' => $url_regex,
'sp_entityid' => $urn_or_url_regex,
renater.salaun
committed
my %actions = ('select_sp' => {'title_en' => 'Select your Service Provider' },
'account_wizard' => {'title_en' => 'Select your Service Provider' },
'generate_token' => {'title_en' => 'Generate an authentication token'},
'validate_token' => {'title_en' => 'Validate an authentication token'},
);
## Gives writes for the group
umask 0002;
chdir $Conf::global{'root_manager_dir'};
my $request = new WebRequest;
if (defined $request) {
$request->execute();
}
$request->respond();
package WebRequest;
## New web request
sub new {
my $pkg = shift;
my $request = {};
&IdPAccountManager::Tools::do_log('info', "");
my $http_query = new CGI;
## Input parameters
my %in_vars = $http_query->Vars;
$request->{'param_in'} = \%in_vars;
## Check if admin acts as another user
$request->{'cookies'} = CGI::Cookie->fetch;
#if (defined $request->{'cookies'}{'as_user'} && $request->{'is_admin'}) {
# $request->{'utilisateur'} = $request->{'as_user'} = $request->{'cookies'}{'as_user'}->value;
# $request->{'is_admin'} = 0;
#}
## Usefull data for output (web pages or mail notices)
$request->{'param_out'}{'url_cgi'} = $ENV{'SCRIPT_NAME'};
$request->{'param_out'}{'env'} = \%ENV;
$request->{'param_out'}{'actions'} = \%actions;
$request->{'param_out'}{'conf'} = \%Conf::global;
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
## Dumping input data
#open TMP, ">/tmp/account_manager.in"; &IdPAccountManager::Tools::dump_var($request->{'param_in'}, 0, \*TMP); close TMP;
## Clean input vars
foreach my $key (keys %{$request->{'param_in'}}) {
#&IdPAccountManager::Tools::do_log('trace', "PARAM_ENTREE: %s=%s", $key, $request->{'param_in'}{$key});
## Removing all ^M (0D)
$request->{'param_in'}{$key} =~ s/\r//g;
$request->{'param_in'}{$key} =~ s/\s+$//; ## Remove trailing spaces
$request->{'param_in'}{$key} =~ s/^\s+//; ## Remove leading spaces
#if ($request->{'param_in'}{$key} =~ /\0/) {
# my @valeurs = split /\0/, $request->{'param_in'}{$key};
# $request->{'param_in'}{$key} = $valeurs[0]; ## Only keep first value of multi-valued parameters
#}
## If action_xx param is set, then action=xx
## Usefull to have sementicless values in submit forms
if ($key =~ /^action_(\w+)$/) {
#&IdPAccountManager::Tools::do_log('trace', "ACTION $key");
$request->{'param_in'}{'action'} = $1;
}
}
## Check the requested action
if ($request->{'param_in'}{'action'} ) {
$request->{'action'} = $request->{'param_in'}{'action'};
}else {
## Default action
&IdPAccountManager::Tools::do_log('info', "Default action");
$request->{'action'} = 'account_wizard';
}
bless $request, $pkg;
return $request;
}
## Execute a web request
sub execute {
my $self = shift;
&IdPAccountManager::Tools::do_log('debug', "");
my $status;
## Check input parameters format
foreach my $key (keys %{$self->{'param_in'}}) {
if ($self->{'param_in'}{$key} !~ /^\s*$/ &&
defined $format{$key} &&
! ref($format{$key})) {
unless ($self->{'param_in'}{$key} =~ /^$format{$key}$/) {
renater.salaun
committed
push @{$self->{'param_out'}{'errors'}}, "format_$key";
&IdPAccountManager::Tools::do_log('error', "Incorrect parameter format : $key");
return undef;
}
}
}
do {
## Actions can be chained
$self->{'action'} = $self->{'next_action'} if ($self->{'next_action'});
delete $self->{'next_action'}; ## Prevent loops
if (defined $actions{$self->{'action'}}) {
## Execute the target subroutine named req_actionName
my $sub = 'req_'.$self->{'action'};
$status = &{$sub}($self);
}else {
## Inknown action
renater.salaun
committed
push @{$self->{'param_out'}{'errors'}}, "unknown_action";
&IdPAccountManager::Tools::do_log('error', "Unknown action '%s'", $self->{'action'});
}
} while ($self->{'next_action'});
#return undef if (!defined $status);
return 1;
}
## Return HTML content
sub respond {
my $self = shift;
&IdPAccountManager::Tools::do_log('debug', "");
## Dump output data
#open TMP, ">/tmp/account_registry.out"; &IdPAccountManager::Tools::dump_var($self->{'param_out'}, 0, \*TMP); close TMP;
renater.salaun
committed
## Enable dumping off all variables in web pages
#$self->{'param_out'}{'dump'} = $self->{'param_out'};
## Automatic pass object entries to the output hash
foreach my $key (keys %{$self}) {
#&IdPAccountManager::Tools::do_log('trace', "Passing $key");
$self->{'param_out'}{$key} ||= $self->{$key} unless ($key eq 'param_out');
}
## An action may redirect to an external URL
if ($self->{'url_redirection'}) {
#&IdPAccountManager::Tools::do_log('trace', "URL Redirect : $self->{'url_redirection'}");
printf "Location: %s\n\n", $self->{'url_redirection'};
}else {
#$self->{'param_out'}{'cookie'} = CGI::Cookie->new(-name=>'as_user',-value=>$self->{'as_user'},-expires=>'-1M');
## Parse template
my $tt2 = Template->new({
ENCODING => 'iso-8859-1', ## le défaut apparemment
FILTERS => {'encode_utf8', => [\&IdPAccountManager::Tools::encode_utf8, 0],
'escape_quotes' => [\&IdPAccountManager::Tools::escape_quotes, 0]},
renater.salaun
committed
INCLUDE_PATH => $Conf::global{'root_manager_dir'}.':'.$Conf::global{'root_manager_dir'}.'/templates/accountProfiles',
#DEBUG => 'all',
#DEBUG => 'caller',
#DEBUG => 'parser'
});
my $template;
## nobanner is used to do AJAX to get only pieces of HTML to load in the web client
if ($self->{'param_in'}{'style'} eq 'nobanner') {
$template = 'templates/web/index-nobanner.tt2.html';
}else {
$template = 'templates/web/index.tt2.html';
}
unless ($tt2->process($template, $self->{'param_out'}, \*STDOUT)) {
printf "Content-type: text/plain\n\n Error: %s", $tt2->error();
&IdPAccountManager::Tools::do_log('error', "Web parser error : %s", $tt2->error());
}
}
## Ignore some type of errors
my @errors_admin;
foreach my $id_error (@{$self->{'param_out'}{'errors'}}) {
unless ($id_error =~ /^(error_x)$/) {
push @errors_admin, $id_error;
}
}
## Mail notification of admins about the error
if (@errors_admin) {
&IdPAccountManager::Tools::mail_notice('template' => 'templates/mail/notification_generic_error.tt2.eml',
'data' => $self->{'param_out'});
}
renater.salaun
committed
}
## Return the list of known SPs first
sub req_account_wizard {
renater.salaun
committed
my $self = shift;
&IdPAccountManager::Tools::do_log('info', "");
renater.salaun
committed
my $federation_metadata = new IdPAccountManager::SAMLMetadata;
unless ($federation_metadata->load(federation_metadata_file_path => $Conf::global{'federation_metadata_file_path'})) {
renater.salaun
committed
push @{$self->{'param_out'}{'errors'}}, "internal";
&IdPAccountManager::Tools::do_log('error', "Failed to load federation metadata : $!");
return undef;
}
unless ($federation_metadata->parse()) {
push @{$self->{'param_out'}{'errors'}}, "internal";
&IdPAccountManager::Tools::do_log('error', "Failed to parse federation metadata : $!");
return undef;
}
$self->{'param_out'}{'federation_metadata_as_hashref'} = $federation_metadata->{'federation_metadata_as_hashref'};
return 1;
}
## Select a Service Provider and return metadata sctucture for the SP
## Sample URL : https://dev-edugain.renater.fr/accountmanager?action=select_sp&sp_entityid=http%3A%2F%2Fsp.lat.csc.fi
sub req_select_sp {
my $self = shift;
&IdPAccountManager::Tools::do_log('info', "");
unless ($self->{'param_in'}{'sp_entityid'}) {
push @{$self->{'param_out'}{'errors'}}, "missing_sp_entityid";
&IdPAccountManager::Tools::do_log('error', "Missing parameter sp_entityid");
return undef;
}
my $federation_metadata = new IdPAccountManager::SAMLMetadata;
unless ($federation_metadata->load(federation_metadata_file_path => $Conf::global{'federation_metadata_file_path'})) {
push @{$self->{'param_out'}{'errors'}}, "internal";
&IdPAccountManager::Tools::do_log('error', "Failed to load federation metadata : $!");
return undef;
}
unless ($federation_metadata->parse(filter_entity_id => $self->{'param_in'}{'sp_entityid'})) {
push @{$self->{'param_out'}{'errors'}}, "internal";
&IdPAccountManager::Tools::do_log('error', "Failed to parse federation metadata : $!");
return undef;
}
$self->{'param_out'}{'sp_metadata_as_hashref'} = $federation_metadata->{'federation_metadata_as_hashref'}[0];
return 1;
}
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
## Generate an authentication token to validate an email address
## Sample call : dev-edugain.renater.fr/accountmanager?action=generate_token&style=nobanner&sp_entityid=https%3A%2F%2Fsourcesup.cru.fr%2Fshibboleth&email_address=support%40renater.fr
sub req_generate_token {
my $self = shift;
&IdPAccountManager::Tools::do_log('info', "");
unless ($self->{'param_in'}{'sp_entityid'}) {
push @{$self->{'param_out'}{'errors'}}, "missing_sp_entityid";
&IdPAccountManager::Tools::do_log('error', "Missing parameter sp_entityid");
return undef;
}
unless ($self->{'param_in'}{'email_address'}) {
push @{$self->{'param_out'}{'errors'}}, "email_address";
&IdPAccountManager::Tools::do_log('error', "Missing parameter email_address");
return undef;
}
my $authentication_token = new IdPAccountManager::AuthenticationToken();
unless (defined $authentication_token) {
push @{$self->{'param_out'}{'errors'}}, "internal";
&IdPAccountManager::Tools::do_log('error', "Failed to create authentication token");
return undef;
}
unless ($authentication_token->set('email_address' => $self->{'param_in'}{'email_address'},
'sp_entityid' => $self->{'param_in'}{'sp_entityid'})) {
push @{$self->{'param_out'}{'errors'}}, "internal";
&IdPAccountManager::Tools::do_log('error', "Failed to update authentication token");
return undef;
}
unless ($authentication_token->save()) {
push @{$self->{'param_out'}{'errors'}}, "internal";
&IdPAccountManager::Tools::do_log('error', "Failed to save authentication token");
return undef;
}
$self->{'param_out'}{'authentication_token'} = $authentication_token->get('token');
$self->{'param_out'}{'email_address'} = $self->{'param_in'}{'email_address'};
$self->{'param_out'}{'sp_entityid'} = $self->{'param_in'}{'sp_entityid'};
$self->{'param_out'}{'to'} = $self->{'param_in'}{'email_address'};
## Send the challenge email with the token
&IdPAccountManager::Tools::mail_notice('template' => 'templates/mail/send_authentication_token.tt2.eml',
'to' => $self->{'param_in'}{'email_address'},
'data' => $self->{'param_out'});
return 1;
}
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
## Validate an authentication token
## Test accounts get created
## Sample call : dev-edugain.renater.fr/accountmanager?action=validate_token&style=nobanner&sp_entityid=https%3A%2F%2Fsourcesup.cru.fr%2Fshibboleth&authentication_token=c1cfecb51ea40d39a695
sub req_validate_token {
my $self = shift;
&IdPAccountManager::Tools::do_log('info', "");
unless ($self->{'param_in'}{'sp_entityid'}) {
push @{$self->{'param_out'}{'errors'}}, "missing_sp_entityid";
&IdPAccountManager::Tools::do_log('error', "Missing parameter sp_entityid");
return undef;
}
unless ($self->{'param_in'}{'authentication_token'}) {
push @{$self->{'param_out'}{'errors'}}, "missing_authentication_token";
&IdPAccountManager::Tools::do_log('error', "Missing parameter authentication_token");
return undef;
}
my $authentication_token = new IdPAccountManager::AuthenticationToken(token => $self->{'param_in'}{'authentication_token'});
unless ($authentication_token->load()) {
push @{$self->{'param_out'}{'errors'}}, "wrong_token";
&IdPAccountManager::Tools::do_log('error', "Failed to validate authentication token %s for sp_entityid %s",
$self->{'param_in'}{'token'}, $self->{'param_in'}{'sp_entityid'});
return undef;
}
unless ($authentication_token->get('sp_entityid') eq $self->{'param_in'}{'sp_entityid'}) {
push @{$self->{'param_out'}{'errors'}}, "wrong_token_for_sp";
&IdPAccountManager::Tools::do_log('error', "Authentication token %s cannot be used for SP with entityid %s",
$self->{'param_in'}{'token'}, $self->{'param_in'}{'sp_entityid'});
return undef;
}
## delete the token
unless ($authentication_token->delete()) {
&IdPAccountManager::Tools::do_log('error', "Failed to delete authentication token %s",
$self->{'param_in'}{'token'});
}
## create test accounts
my @test_accounts = &IdPAccountManager::TestAccount::create_test_accounts_for_sp(sp_entityid => $self->{'param_in'}{'sp_entityid'});
unless (@test_accounts) {
push @{$self->{'param_out'}{'errors'}}, "accounts_creation_failed";
&IdPAccountManager::Tools::do_log('error', "Failed to create test accounts for SP with entityid %s",
$self->{'param_in'}{'sp_entityid'});
return undef;
}
## Update simpleSAMLphp configuration to enable test accounts
unless (&IdPAccountManager::Tools::update_ssp_authsources()) {
push @{$self->{'param_out'}{'errors'}}, "accounts_creation_failed";
&IdPAccountManager::Tools::do_log('error', "Failed to create simpleSAMLphp configuration file");
return undef;
}
$self->{'param_out'}{'sp_entityid'} = $self->{'param_in'}{'sp_entityid'};
$self->{'param_out'}{'test_accounts'} = \@test_accounts;
return 1;
}