From 4f9e7869d784e02484d95083def80ae0f3caff55 Mon Sep 17 00:00:00 2001 From: Guillaume Rousse <guillaume.rousse@renater.fr> Date: Tue, 31 Oct 2017 11:49:20 +0100 Subject: [PATCH] move WebRequest package into its own file --- bin/account-manager-web.pl | 561 +--------------------------- lib/IdPAccountManager/WebRequest.pm | 556 +++++++++++++++++++++++++++ 2 files changed, 561 insertions(+), 556 deletions(-) create mode 100755 lib/IdPAccountManager/WebRequest.pm diff --git a/bin/account-manager-web.pl b/bin/account-manager-web.pl index b4af672..57cadd9 100755 --- a/bin/account-manager-web.pl +++ b/bin/account-manager-web.pl @@ -23,6 +23,7 @@ use IdPAccountManager::SAMLMetadata; use IdPAccountManager::ServiceProvider; use IdPAccountManager::AuthenticationToken; +use IdPAccountManager::WebRequest; ## Defining parameters format my $urn_or_url_regex = '(http(s?):\/\/|urn:)[^\\\$\*\"\'\`\^\|\<\>\n\s]+' @@ -49,565 +50,13 @@ umask 0002; chdir $Conf::global{'root_manager_dir'}; -my $request = new WebRequest; +my $request = new IdPAccountManager::WebRequest( + actions => \%actions, + format => \%format +); 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; - - ## 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'} = 'home'; - } - - 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}$/) { - 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 - no strict 'refs'; - my $sub = 'req_' . $self->{'action'}; - $status = &{$sub}($self); - } else { - ## Inknown action - 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; - - ## 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 ] - }, - 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) { - $self->{'param_out'}{'subject'} = 'Error notification - web interface'; - IdPAccountManager::Tools::mail_notice( - 'template' => 'templates/mail/notification_generic_error.tt2.eml', - 'data' => $self->{'param_out'} - ); - } -} - -## Return the list of known SPs first -sub req_account_wizard { - my $self = shift; - IdPAccountManager::Tools::do_log('info', ""); - - 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()) { - 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; - } - - ## Create a serviceprovider object to store major parameters for this SP in DB - my $service_provider = new IdPAccountManager::ServiceProvider( - entityid => $self->{'param_in'}{'sp_entityid'}); - - ## Prepare data -#open TMP, ">/tmp/account_manager_metadata.dump"; IdPAccountManager::Tools::dump_var($federation_metadata->{'federation_metadata_as_hashref'}[0], 0, \*TMP); close TMP; - my $sp_metadata_as_hashref = - $federation_metadata->{'federation_metadata_as_hashref'}[0]; - my @contacts; - if (defined $sp_metadata_as_hashref->{'contacts'}) { - foreach my $contact (@{ $sp_metadata_as_hashref->{'contacts'} }) { - my $email = $contact->{'EmailAddress'}; - $email =~ s/^(mailto:)//; ## Remove 'mailto:' prefixes if any - push @contacts, $email; - } - } - my $display_name; - if (defined $sp_metadata_as_hashref->{'display_name'}) { - ## Use English version of displayName if available - if ($sp_metadata_as_hashref->{'display_name'}{'en'}) { - $display_name = $sp_metadata_as_hashref->{'display_name'}{'en'}; - ## Else any language - } else { - foreach - my $lang (keys %{ $sp_metadata_as_hashref->{'display_name'} }) - { -#IdPAccountManager::Tools::do_log('TRACE', "Display name(%s): %s", $lang, $sp_metadata_as_hashref->{'display_name'}{$lang}); - $display_name = - $sp_metadata_as_hashref->{'display_name'}{$lang}; - last; - } - } - } - - ## Try loading DB object first - if ($service_provider->load(speculative => 1)) { - $service_provider->contacts(join(',', @contacts)); - $service_provider->displayname($display_name); - - } else { - - $service_provider = new IdPAccountManager::ServiceProvider( - entityid => $self->{'param_in'}{'sp_entityid'}, - contacts => join(',', @contacts), - displayname => $display_name - ); - unless (defined $service_provider) { - push @{ $self->{'param_out'}{'errors'} }, "internal"; - IdPAccountManager::Tools::do_log('error', - "Failed to create serviceprovider object"); - return undef; - } - } - - unless ($service_provider->save()) { - push @{ $self->{'param_out'}{'errors'} }, "internal"; - IdPAccountManager::Tools::do_log('error', - "Failed to save serviceprovider object"); - return undef; - } - - $self->{'param_out'}{'sp_metadata_as_hashref'} = - $federation_metadata->{'federation_metadata_as_hashref'}[0]; - $self->{'param_out'}{'serviceprovider'} = $service_provider; - - return 1; -} - -## 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; - } - - ## Create a serviceprovider object to load parameters for this SP from DB - my $service_provider = new IdPAccountManager::ServiceProvider( - entityid => $self->{'param_in'}{'sp_entityid'}); - - # Try loading DB object first - unless ($service_provider->load(speculative => 1)) { - push @{ $self->{'param_out'}{'errors'} }, "internal"; - IdPAccountManager::Tools::do_log( - 'error', - "Failed to load SP with entityid '%s'", - $self->{'param_in'}{'sp_entityid'} - ); - return undef; - } - - ## Check that email_address is a known contact for this SP - unless ($service_provider->is_contact($self->{'param_in'}{'email_address'})) - { - push @{ $self->{'param_out'}{'errors'} }, "internal"; - IdPAccountManager::Tools::do_log( - 'error', - "Requested a token for %s for an unautorized address '%s'", - $self->{'param_in'}{'sp_entityid'}, - $self->{'param_in'}{'email_address'} - ); - return undef; - } - - my $authentication_token = new IdPAccountManager::AuthenticationToken( - 'email_address' => $self->{'param_in'}{'email_address'}, - 'sp_entityid' => $self->{'param_in'}{'sp_entityid'} - ); - unless (defined $authentication_token) { - push @{ $self->{'param_out'}{'errors'} }, "internal"; - IdPAccountManager::Tools::do_log('error', - "Failed to create authentication token"); - return undef; - } - - ## First remove token if one exist for this email+SP - if ($authentication_token->load()) { - unless ($authentication_token->delete()) { - push @{ $self->{'param_out'}{'errors'} }, "internal"; - IdPAccountManager::Tools::do_log( - 'error', - "Failed to delete previous authentication token with ID %s", - $authentication_token->get('id') - ); - return undef; - } - - $authentication_token = new IdPAccountManager::AuthenticationToken( - 'email_address' => $self->{'param_in'}{'email_address'}, - 'sp_entityid' => $self->{'param_in'}{'sp_entityid'} - ); - 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->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'} - ); - - IdPAccountManager::Tools::do_log( - 'info', - "Token send to %s for sp_entityid=%s;token=%s", - $self->{'param_in'}{'email_address'}, - $self->{'param_in'}{'sp_entityid'}, - $self->{'param_out'}{'authentication_token'} - ); - - return 1; -} - -## 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'}{'authentication_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'}{'authentication_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'}{'authentication_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; - } - - IdPAccountManager::Tools::do_log( - 'info', - "Token validated for sp_entityid=%s;token=%s", - $self->{'param_in'}{'sp_entityid'}, - $self->{'param_in'}{'authentication_token'} - ); - - $self->{'param_out'}{'sp_entityid'} = $self->{'param_in'}{'sp_entityid'}; - $self->{'param_out'}{'test_accounts'} = \@test_accounts; - - return 1; -} - -## Return the homepage of the service -sub req_home { - my $self = shift; - IdPAccountManager::Tools::do_log('info', ""); - - return 1; -} diff --git a/lib/IdPAccountManager/WebRequest.pm b/lib/IdPAccountManager/WebRequest.pm new file mode 100755 index 0000000..de1c370 --- /dev/null +++ b/lib/IdPAccountManager/WebRequest.pm @@ -0,0 +1,556 @@ +package IdPAccountManager::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; + + ## 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'} = 'home'; + } + + 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}$/) { + 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 + no strict 'refs'; + my $sub = 'req_' . $self->{'action'}; + $status = &{$sub}($self); + } else { + ## Inknown action + 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; + + ## 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 ] + }, + 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) { + $self->{'param_out'}{'subject'} = 'Error notification - web interface'; + IdPAccountManager::Tools::mail_notice( + 'template' => 'templates/mail/notification_generic_error.tt2.eml', + 'data' => $self->{'param_out'} + ); + } +} + +## Return the list of known SPs first +sub req_account_wizard { + my $self = shift; + IdPAccountManager::Tools::do_log('info', ""); + + 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()) { + 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; + } + + ## Create a serviceprovider object to store major parameters for this SP in DB + my $service_provider = new IdPAccountManager::ServiceProvider( + entityid => $self->{'param_in'}{'sp_entityid'}); + + ## Prepare data +#open TMP, ">/tmp/account_manager_metadata.dump"; IdPAccountManager::Tools::dump_var($federation_metadata->{'federation_metadata_as_hashref'}[0], 0, \*TMP); close TMP; + my $sp_metadata_as_hashref = + $federation_metadata->{'federation_metadata_as_hashref'}[0]; + my @contacts; + if (defined $sp_metadata_as_hashref->{'contacts'}) { + foreach my $contact (@{ $sp_metadata_as_hashref->{'contacts'} }) { + my $email = $contact->{'EmailAddress'}; + $email =~ s/^(mailto:)//; ## Remove 'mailto:' prefixes if any + push @contacts, $email; + } + } + my $display_name; + if (defined $sp_metadata_as_hashref->{'display_name'}) { + ## Use English version of displayName if available + if ($sp_metadata_as_hashref->{'display_name'}{'en'}) { + $display_name = $sp_metadata_as_hashref->{'display_name'}{'en'}; + ## Else any language + } else { + foreach + my $lang (keys %{ $sp_metadata_as_hashref->{'display_name'} }) + { +#IdPAccountManager::Tools::do_log('TRACE', "Display name(%s): %s", $lang, $sp_metadata_as_hashref->{'display_name'}{$lang}); + $display_name = + $sp_metadata_as_hashref->{'display_name'}{$lang}; + last; + } + } + } + + ## Try loading DB object first + if ($service_provider->load(speculative => 1)) { + $service_provider->contacts(join(',', @contacts)); + $service_provider->displayname($display_name); + + } else { + + $service_provider = new IdPAccountManager::ServiceProvider( + entityid => $self->{'param_in'}{'sp_entityid'}, + contacts => join(',', @contacts), + displayname => $display_name + ); + unless (defined $service_provider) { + push @{ $self->{'param_out'}{'errors'} }, "internal"; + IdPAccountManager::Tools::do_log('error', + "Failed to create serviceprovider object"); + return undef; + } + } + + unless ($service_provider->save()) { + push @{ $self->{'param_out'}{'errors'} }, "internal"; + IdPAccountManager::Tools::do_log('error', + "Failed to save serviceprovider object"); + return undef; + } + + $self->{'param_out'}{'sp_metadata_as_hashref'} = + $federation_metadata->{'federation_metadata_as_hashref'}[0]; + $self->{'param_out'}{'serviceprovider'} = $service_provider; + + return 1; +} + +## 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; + } + + ## Create a serviceprovider object to load parameters for this SP from DB + my $service_provider = new IdPAccountManager::ServiceProvider( + entityid => $self->{'param_in'}{'sp_entityid'}); + + # Try loading DB object first + unless ($service_provider->load(speculative => 1)) { + push @{ $self->{'param_out'}{'errors'} }, "internal"; + IdPAccountManager::Tools::do_log( + 'error', + "Failed to load SP with entityid '%s'", + $self->{'param_in'}{'sp_entityid'} + ); + return undef; + } + + ## Check that email_address is a known contact for this SP + unless ($service_provider->is_contact($self->{'param_in'}{'email_address'})) + { + push @{ $self->{'param_out'}{'errors'} }, "internal"; + IdPAccountManager::Tools::do_log( + 'error', + "Requested a token for %s for an unautorized address '%s'", + $self->{'param_in'}{'sp_entityid'}, + $self->{'param_in'}{'email_address'} + ); + return undef; + } + + my $authentication_token = new IdPAccountManager::AuthenticationToken( + 'email_address' => $self->{'param_in'}{'email_address'}, + 'sp_entityid' => $self->{'param_in'}{'sp_entityid'} + ); + unless (defined $authentication_token) { + push @{ $self->{'param_out'}{'errors'} }, "internal"; + IdPAccountManager::Tools::do_log('error', + "Failed to create authentication token"); + return undef; + } + + ## First remove token if one exist for this email+SP + if ($authentication_token->load()) { + unless ($authentication_token->delete()) { + push @{ $self->{'param_out'}{'errors'} }, "internal"; + IdPAccountManager::Tools::do_log( + 'error', + "Failed to delete previous authentication token with ID %s", + $authentication_token->get('id') + ); + return undef; + } + + $authentication_token = new IdPAccountManager::AuthenticationToken( + 'email_address' => $self->{'param_in'}{'email_address'}, + 'sp_entityid' => $self->{'param_in'}{'sp_entityid'} + ); + 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->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'} + ); + + IdPAccountManager::Tools::do_log( + 'info', + "Token send to %s for sp_entityid=%s;token=%s", + $self->{'param_in'}{'email_address'}, + $self->{'param_in'}{'sp_entityid'}, + $self->{'param_out'}{'authentication_token'} + ); + + return 1; +} + +## 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'}{'authentication_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'}{'authentication_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'}{'authentication_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; + } + + IdPAccountManager::Tools::do_log( + 'info', + "Token validated for sp_entityid=%s;token=%s", + $self->{'param_in'}{'sp_entityid'}, + $self->{'param_in'}{'authentication_token'} + ); + + $self->{'param_out'}{'sp_entityid'} = $self->{'param_in'}{'sp_entityid'}; + $self->{'param_out'}{'test_accounts'} = \@test_accounts; + + return 1; +} + +## Return the homepage of the service +sub req_home { + my $self = shift; + IdPAccountManager::Tools::do_log('info', ""); + + return 1; +} + +1; -- GitLab