diff --git a/lib/AccessCheck/App.pm b/lib/AccessCheck/App.pm index 350f4cea7fb7b6af52fb84de234005dbc449915a..16cfbc1ab2f3f056e3fbdebdcba4e117c7834d75 100644 --- a/lib/AccessCheck/App.pm +++ b/lib/AccessCheck/App.pm @@ -90,7 +90,8 @@ sub startup { $routes->get('/status')->to(controller => 'status', action => 'run')->name('status'); $routes->get('/step1')->to(controller => 'step1', action => 'run')->name('step1'); $routes->get('/step2')->to(controller => 'step2', action => 'run')->name('step2'); - $routes->get('/step3')->to(controller => 'step3', action => 'run')->name('step3'); + $routes->get('/send_challenge')->to(controller => 'send_challenge', action => 'run')->name('send_challenge'); + $routes->get('/validate_challenge')->to(controller => 'validate_challenge', action => 'run')->name('validate_challenge'); $routes->get('/step4')->to(controller => 'step4', action => 'run')->name('step4'); $routes->get('/step5')->to(controller => 'step5', action => 'run')->name('step5'); diff --git a/lib/AccessCheck/App/Step3.pm b/lib/AccessCheck/App/SendChallenge.pm similarity index 89% rename from lib/AccessCheck/App/Step3.pm rename to lib/AccessCheck/App/SendChallenge.pm index b97bf07d34abaaeeb09eaa37890a6ee87eb7b524..18035167f7df1117e54bb1d63171ccef0e407c19 100644 --- a/lib/AccessCheck/App/Step3.pm +++ b/lib/AccessCheck/App/SendChallenge.pm @@ -1,4 +1,4 @@ -package AccessCheck::App::Step3; +package AccessCheck::App::SendChallenge; use Mojo::Base qw(AccessCheck::App::Controller); @@ -119,7 +119,7 @@ sub run { sp => { entityid => $entityid, }, to => $email, token => $token->secret(), - challenge_url => $self->url_for('step3')->query(entityid => $entityid, email => $email, token => $self->csrf_token())->to_abs(), + challenge_url => $self->url_for('validate_challenge')->query(entityid => $entityid, email => $email)->to_abs(), lh => $l10n }; my $text_content; @@ -173,22 +173,8 @@ sub run { ) ); - my $profiles = $base_templates_dir - ->child('accounts') - ->list() - ->map(sub { m/([^\/]+).tt2$/}) - ->to_array(); - - $self->stash(entityid => $entityid); - $self->stash(email => $email); - $self->stash(validity => $config->{service}->{account_validity_period}); - $self->stash(profiles => $profiles); - - $self->render( - status => 200, - template => 'step3', - format => 'html' - ); + $self->redirect_to('validate_challenge', email => $email, entityid => $entityid); + } 1; diff --git a/lib/AccessCheck/App/ValidateChallenge.pm b/lib/AccessCheck/App/ValidateChallenge.pm new file mode 100644 index 0000000000000000000000000000000000000000..d0295d8dd523d2b6350372e48f7f244554e69c28 --- /dev/null +++ b/lib/AccessCheck/App/ValidateChallenge.pm @@ -0,0 +1,66 @@ +package AccessCheck::App::ValidateChallenge; + +use Mojo::Base qw(AccessCheck::App::Controller); + +use DateTime; +use Email::MIME; +use Email::Sender::Simple; +use English qw(-no_match_vars); +use Syntax::Keyword::Try; +use Template::Constants qw(:chomp); + +use AccessCheck::Data::Token; +use AccessCheck::Regexp; +use AccessCheck::Tools; + +sub run { + my $self = shift; + + my $app = $self->app(); + my $config = $app->config(); + my $log = $app->log(); + + my $l10n = $self->init_l10n(); + my $user = $self->init_user(); + my $db = $self->init_db(); + + if ($config->{app}->{login_url}) { + return if !$self->check_authentication(); + } + + my $entityid = $self->param('entityid'); + my $email = $self->param('email'); + + my $sp = $self->get_sp(entityid => $entityid); + return if !$sp; + + return $self->abort( + log_message => "Missing parameter: email", + user_message => "missing_email" + ) if !$email; + + return $self->abort( + log_message => "Invalid parameter: email", + user_message => "invalid_email" + ) if $email !~ $AccessCheck::Regexp::email; + + my $base_templates_dir = $self->app()->home()->child('templates'); + my $profiles = $base_templates_dir + ->child('accounts') + ->list() + ->map(sub { m/([^\/]+).tt2$/}) + ->to_array(); + + $self->stash(entityid => $entityid); + $self->stash(email => $email); + $self->stash(validity => $config->{service}->{account_validity_period}); + $self->stash(profiles => $profiles); + + $self->render( + status => 200, + template => 'validate_challenge', + format => 'html' + ); +} + +1; diff --git a/lib/Makefile.am b/lib/Makefile.am index cce6d0dde611c0b213abba397205aa6dc1034163..9c90300f04a636df23fd0e13ec5dec780e82d4d6 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -18,7 +18,8 @@ nobase_applib_DATA = \ AccessCheck/App/Status.pm \ AccessCheck/App/Step1.pm \ AccessCheck/App/Step2.pm \ - AccessCheck/App/Step3.pm \ + AccessCheck/App/SendChallenge.pm \ + AccessCheck/App/ValidateChallenge.pm \ AccessCheck/App/Step4.pm \ AccessCheck/App/Step5.pm \ AccessCheck/Template/Plugin/Quote.pm diff --git a/t/app.t b/t/app.t index 1985c77761e1615aba1153c47705acefeedddf05..01fc8c197f19d44dc1361d67740e214c3f45bb2c 100755 --- a/t/app.t +++ b/t/app.t @@ -20,7 +20,7 @@ plan(skip_all => 'live database required') unless $ENV{TEST_DB_NAME} && $ENV{TEST_DB_TYPE}; -plan tests => 11; +plan tests => 16; sub named_subtest { my ($name, $code, @args) = @_; @@ -224,10 +224,10 @@ named_subtest "email selection page, valid entityid" => sub { html_ok($res) or diag_file($res, $test_dir); }; -named_subtest "challenge page, missing CSRF token" => sub { +named_subtest "challenge sending page, missing CSRF token" => sub { my $t = get_test_object(test => $_[0]); - $t->get_ok('/step3') + $t->get_ok('/send_challenge') ->status_is(403) ->text_is('html head title' => 'eduGAIN Access Check', 'expected title') ->content_like(qr/Error:[\n\s]+missing CSRF token/, 'expected error message'); @@ -236,13 +236,13 @@ named_subtest "challenge page, missing CSRF token" => sub { html_ok($res) or diag_file($res, $test_dir); }; -named_subtest "challenge page, missing entityid" => sub { +named_subtest "challenge sending page, missing entityid" => sub { my $t = get_test_object(test => $_[0]); # neutralize CSRF token check, as we short-circuit the form $t->app()->hook(before_dispatch => sub { $_[0]->session(csrf_token => 'foo') }); - $t->get_ok('/step3' => form => {token => 'foo'}) + $t->get_ok('/send_challenge' => form => {token => 'foo'}) ->status_is(200) ->text_is('html head title' => 'eduGAIN Access Check', 'expected title') ->content_like(qr/Error:[\n\s]+missing parameter 'entityid'/, 'expected error message'); @@ -251,13 +251,13 @@ named_subtest "challenge page, missing entityid" => sub { html_ok($res) or diag_file($res, $test_dir); }; -named_subtest "challenge page, invalid entityid" => sub { +named_subtest "challenge sending page, invalid entityid" => sub { my $t = get_test_object(test => $_[0]); # neutralize CSRF token check, as we short-circuit the form $t->app()->hook(before_dispatch => sub { $_[0]->session(csrf_token => 'foo') }); - $t->get_ok('/step3' => form => {token => 'foo', entityid => 'foo'}) + $t->get_ok('/send_challenge' => form => {token => 'foo', entityid => 'foo'}) ->status_is(200) ->text_is('html head title' => 'eduGAIN Access Check', 'expected title') ->content_like(qr/Error:[\n\s]+invalid parameter 'entityid'/, 'expected error message'); @@ -266,13 +266,13 @@ named_subtest "challenge page, invalid entityid" => sub { html_ok($res) or diag_file($res, $test_dir); }; -named_subtest "challenge page, valid entityid, missing email" => sub { +named_subtest "challenge sending page, valid entityid, missing email" => sub { my $t = get_test_object(test => $_[0]); # neutralize CSRF token check, as we short-circuit the form $t->app()->hook(before_dispatch => sub { $_[0]->session(csrf_token => 'foo') }); - $t->get_ok('/step3' => form => {token => 'foo', entityid => 'https://sp.renater.fr/'}) + $t->get_ok('/send_challenge' => form => {token => 'foo', entityid => 'https://sp.renater.fr/'}) ->status_is(200) ->text_is('html head title' => 'eduGAIN Access Check', 'expected title') ->content_like(qr/Error:[\n\s]+missing parameter 'email'/, 'expected error message'); @@ -281,13 +281,13 @@ named_subtest "challenge page, valid entityid, missing email" => sub { html_ok($res) or diag_file($res, $test_dir); }; -named_subtest "challenge page, valid entityid, invalid email" => sub { +named_subtest "challenge sending page, valid entityid, invalid email" => sub { my $t = get_test_object(test => $_[0]); # neutralize CSRF token check, as we short-circuit the form $t->app()->hook(before_dispatch => sub { $_[0]->session(csrf_token => 'foo') }); - $t->get_ok('/step3' => form => {token => 'foo', entityid => 'https://sp.renater.fr/', email => 'foo'}) + $t->get_ok('/send_challenge' => form => {token => 'foo', entityid => 'https://sp.renater.fr/', email => 'foo'}) ->status_is(200) ->text_is('html head title' => 'eduGAIN Access Check', 'expected title') ->content_like(qr/Error:[\n\s]+invalid parameter 'email'/, 'expected error message'); @@ -296,13 +296,73 @@ named_subtest "challenge page, valid entityid, invalid email" => sub { html_ok($res) or diag_file($res, $test_dir); }; +named_subtest "challenge validation page, missing entityid" => sub { + my $t = get_test_object(test => $_[0]); + + $t->get_ok('/validate_challenge') + ->status_is(200) + ->text_is('html head title' => 'eduGAIN Access Check', 'expected title') + ->content_like(qr/Error:[\n\s]+missing parameter 'entityid'/, 'expected error message'); + + my $res = $t->tx()->res(); + html_ok($res) or diag_file($res, $test_dir); +}; + +named_subtest "challenge validation page, invalid entityid" => sub { + my $t = get_test_object(test => $_[0]); + + $t->get_ok('/validate_challenge' => form => {entityid => 'foo'}) + ->status_is(200) + ->text_is('html head title' => 'eduGAIN Access Check', 'expected title') + ->content_like(qr/Error:[\n\s]+invalid parameter 'entityid'/, 'expected error message'); + + my $res = $t->tx()->res(); + html_ok($res) or diag_file($res, $test_dir); +}; + +named_subtest "challenge validation page, valid entityid, missing email" => sub { + my $t = get_test_object(test => $_[0]); + + $t->get_ok('/validate_challenge' => form => {entityid => 'https://sp.renater.fr/'}) + ->status_is(200) + ->text_is('html head title' => 'eduGAIN Access Check', 'expected title') + ->content_like(qr/Error:[\n\s]+missing parameter 'email'/, 'expected error message'); + + my $res = $t->tx()->res(); + html_ok($res) or diag_file($res, $test_dir); +}; + +named_subtest "challenge validation page, valid entityid, invalid email" => sub { + my $t = get_test_object(test => $_[0]); + + $t->get_ok('/validate_challenge' => form => {entityid => 'https://sp.renater.fr/', email => 'foo'}) + ->status_is(200) + ->text_is('html head title' => 'eduGAIN Access Check', 'expected title') + ->content_like(qr/Error:[\n\s]+invalid parameter 'email'/, 'expected error message'); + + my $res = $t->tx()->res(); + html_ok($res) or diag_file($res, $test_dir); +}; + +named_subtest "challenge validation page, valid entityid, valid email" => sub { + my $t = get_test_object(test => $_[0]); + + $t->get_ok('/validate_challenge' => form => {entityid => 'https://sp.renater.fr/', email => 'contact1@renater.fr'}) + ->status_is(200) + ->text_is('html head title' => 'eduGAIN Access Check', 'expected title') + ->text_is('div.content h2' => 'Complete email challenge', 'expected form title'); + + my $res = $t->tx()->res(); + html_ok($res) or diag_file($res, $test_dir); +}; + named_subtest "index page, french version" => sub { my $t = get_test_object(test => $_[0], language => 'fr-FR,fr;q=0.9'); $t->get_ok('/') ->status_is(200) ->text_is('html head title' => 'eduGAIN Access Check') - ->text_is('a[href=/step1]' => 'Commencer', 'get started button'); + ->text_is('html body a[href=/step1]' => 'Commencer', 'get started button'); my $res = $t->tx()->res(); html_ok($res) or diag_file($res, $test_dir); diff --git a/templates/web/edugain/step2.html.tt2 b/templates/web/edugain/step2.html.tt2 index 862d5e6bcc6be16e3cf52286971e752826fd4893..c544c15158530eea36a7ff6a2534cc5d1fbfb27f 100644 --- a/templates/web/edugain/step2.html.tt2 +++ b/templates/web/edugain/step2.html.tt2 @@ -1,5 +1,5 @@ [% WRAPPER index.html.tt2 %] -<form class="wizard clearfix" action="[% c.url_for('step3') %]" method="get"> +<form class="wizard clearfix" action="[% c.url_for('send_challenge') %]" method="get"> <input type="hidden" name="token" value="[% c.csrf_token() %]"> <div class="steps clearfix"> <ol> diff --git a/templates/web/edugain/step3.html.tt2 b/templates/web/edugain/validate_challenge.html.tt2 similarity index 100% rename from templates/web/edugain/step3.html.tt2 rename to templates/web/edugain/validate_challenge.html.tt2 diff --git a/templates/web/renater/step2.html.tt2 b/templates/web/renater/step2.html.tt2 index 12e6860587c1fb7e49d654e2785f1982f8c09bd9..cd73dfcbaa5cec47b6329cbcf67bf7d8834755db 100644 --- a/templates/web/renater/step2.html.tt2 +++ b/templates/web/renater/step2.html.tt2 @@ -1,5 +1,5 @@ [% WRAPPER index.html.tt2 %] -<form class="wizard clearfix" action="[% c.url_for('step3') %]" method="get"> +<form class="wizard clearfix" action="[% c.url_for('send_challenge') %]" method="get"> <input type="hidden" name="token" value="[% c.csrf_token() %]"> <div class="steps clearfix"> <ol> diff --git a/templates/web/renater/step3.html.tt2 b/templates/web/renater/validate_challenge.html.tt2 similarity index 100% rename from templates/web/renater/step3.html.tt2 rename to templates/web/renater/validate_challenge.html.tt2