From 5f8c9ba5f32c0dab7752a7c50dab5b82639f81ef Mon Sep 17 00:00:00 2001 From: Romain d'Alverny Date: Fri, 7 Jan 2011 23:20:14 +0000 Subject: commit implement forgot_password patch from Maarten Vanraes --- catdap.yml | 7 + lib/CatDap/Controller/forgot_password.pm | 270 +++++++++++++++++++++++++++++++ root/email/forgot_password.tt | 7 + root/forgot_password/check.tt | 4 + root/forgot_password/complete.tt | 6 + root/forgot_password/confirm.tt | 21 +++ root/forgot_password/index.tt | 17 ++ root/index.tt | 3 +- 8 files changed, 333 insertions(+), 2 deletions(-) create mode 100644 lib/CatDap/Controller/forgot_password.pm create mode 100644 root/email/forgot_password.tt create mode 100644 root/forgot_password/check.tt create mode 100644 root/forgot_password/complete.tt create mode 100644 root/forgot_password/confirm.tt create mode 100644 root/forgot_password/index.tt diff --git a/catdap.yml b/catdap.yml index 88dcfe8..671fb12 100644 --- a/catdap.yml +++ b/catdap.yml @@ -35,6 +35,12 @@ register: login_blacklist: - apache +forgot_password: + secret: + path: '/tmp/' + prefix: 'catdap-forgot_password-' + timeout: 259200 + authentication: default_realm: ldap realms: @@ -55,6 +61,7 @@ authentication: user_filter: '(&(objectClass=inetOrgPerson)(uid=%s))' user_scope: 'one' user_field: 'uid' + email_filter: '(&(objectClass=inetOrgPerson)(|(mail=%s)(mailAlternateAddress=%s)))' use_roles: 1 role_basedn: 'dc=mageia,dc=org' role_scope: 'sub' diff --git a/lib/CatDap/Controller/forgot_password.pm b/lib/CatDap/Controller/forgot_password.pm new file mode 100644 index 0000000..b5dde29 --- /dev/null +++ b/lib/CatDap/Controller/forgot_password.pm @@ -0,0 +1,270 @@ +package CatDap::Controller::forgot_password; +use Moose; +use namespace::autoclean; +use Email::Valid; +use Data::UUID; + +BEGIN {extends 'Catalyst::Controller'; } + +=head1 NAME + +CatDap::Controller::forgot_password - Catalyst Controller + +=head1 DESCRIPTION + +Catalyst Controller. + +=head1 METHODS + +=cut + + +=head2 index + +=cut + +sub index :Path :Args(0) : Form { + my ( $self, $c ) = @_; + + if (defined $c->user) { + # if we're logged in, we haven't forgotten the password + $c->log->debug('Redirecting to /user'); + $c->res->redirect('/user'); + } +} + +sub check : Local { + my ( $self, $c ) = @_; + + my %details = %{$c->request->params}; + my $username = lc($c->request->params->{uid}); + my @errors; + $c->stash(errors => []); + my $email = $c->request->params->{mail}; + if (! Email::Valid->address($email)) { + push @errors, $c->loc('Invalid email address'); + } + + if (@errors) { + $c->stash(errors => \@errors); + $c->stash(template => 'forgot_password/index.tt'); + return; + } + + # check in LDAP now that we have validated username and email + my $emailfilter = $c->config->{'authentication'}{'realms'}{'ldap'}{'store'}{'email_filter'}; + $emailfilter =~ s/\%s/$email/g, + $c->log->debug("Searching for email $email with filter $emailfilter"); + my $mesg = $c->model('Proxy')->search($emailfilter); + if (!$mesg->entries()) { + push @errors,$c->loc( + 'This email address is not bound to an account' + ); + } + + if (@errors) { + $c->stash(errors => \@errors); + $c->stash(template => 'forgot_password/index.tt'); + return; + } + + if ($mesg->code) { + push @errors,$mesg->error; + $c->log->info( sprintf("finding email $email failed: %s", $mesg->error) ); + $c->stash(errors => \@errors); + $c->stash(template => 'register/index.tt'); + return; + } + + my $secret = gen_secret($c, $email); + + $c->stash( + email => { + 'to' => $email, + 'from' => ${$c->config}{'emailfrom'}, + 'subject' => ${$c->config}{'apptitle'} . " - " . $c->loc('Forgot password'), + 'template' => 'forgot_password.tt', + }, + url => $c->uri_for('/forgot_password/confirm') . "?secret=$secret", + ); + + $c->log->info("Sending forgot password mail to email address $email"); + $c->forward( $c->view('Email::Template') ); + if ( @{ $c->error } ) { + my $errors = join "\n",@{ $c->error }; + $c->log->info("Sending activation mail to $email failed: $errors"); + $c->response->body($c->loc('An error occured sending the email, please try again later. Errors [_1]', $errors)); + $c->error(0); # Reset the error condition if you need to + } + $c->stash(template => 'forgot_password/complete.tt'); +} + +sub confirm : Local { + my ($self, $c) = @_; + my $secret = $c->req->param('secret'); + my @errors; + + # show confirm page which can enter new password + if (defined $c->user) { + # if we're logged in, we haven't forgotten the password + $c->log->debug('Redirecting to /user'); + $c->res->redirect('/user'); + } + + # find secret + my $email = find_secret($c, $secret); + if (!$email) { + push @errors, "Secret has expired, please try again."; + $c->stash(errors => \@errors); + $c->stash(template => 'forgot_password/index.tt'); + return; + } + my $mesg = find_user_email($c, $email); + if ($mesg->code) { + push @errors, "Secret has expired, please try again."; + $c->stash(errors => \@errors); + $c->stash(template => 'forgot_password/index.tt'); + return; + } + + # show template to enter a new password + $c->stash(secret => $secret, template => 'forgot_password/confirm.tt'); +} + +sub change_password : Local { + my ($self, $c) = @_; + my @errors = (); + my $secret = $c->req->param('secret'); + my $newpass; + + # find secret + my $email = find_secret($c, $secret); + if (!$email) { + push @errors, "Secret has expired, please try again."; + $c->stash(errors => \@errors); + $c->stash(template => 'forgot_password/index.tt'); + return; + } + my $mesg = find_user_email($c, $email); + if ( $mesg->code) { + push @errors, "Secret has expired, please try again."; + $c->stash(errors => \@errors); + $c->stash(template => 'forgot_password/index.tt'); + return; + } + my $entry = $mesg->entry; + + # check if both passwords are equal and are confirm the validation norms + if ($c->req->param('newpassword1') eq $c->req->param('newpassword2')) { + $newpass = $c->req->param('newpassword1'); + } else { + push @errors, "New passwords dont match"; + } + # if error show confirm page again to retry + if (@errors) { + $c->stash(errors => \@errors); + $c->stash(template => 'forgot_password/confirm.tt'); + return; + } + + # change password + my $pp = Net::LDAP::Control::PasswordPolicy->new; + $mesg = $c->model('Proxy')->set_password( + user => $entry->dn, + newpasswd => $newpass, + control => [ $pp ], + ); + if ($mesg->code) { + my $perror = $mesg->error; + push @errors, "Password change failed: $perror"; + $c->detach; + } + + # if error show confirm page again to retry + if (@errors) { + $c->stash(errors => \@errors); + $c->stash(template => 'forgot_password/confirm.tt'); + return; + } + + # TODO: log in by setting the $c->user + + # remove the stored secret + remove_secret($c, $secret); + + # redirect to / + $c->log->debug('Redirecting to /'); + $c->res->redirect('/'); +} + +sub gen_secret { + my ($c, $email) = @_; + my $ug = new Data::UUID; + # generate a unique secret + my $secret = $ug->create_str(); + my $filename = $c->config->{'forgot_password'}{'secret'}{'path'} .'/'. $c->config->{'forgot_password'}{'secret'}{'prefix'} . $secret; + # store secret with email + open FILE, ">$filename"; + print FILE $email; + close FILE; + return $secret; +} + +sub find_secret { + my ($c, $secret) = @_; + my $email; + my $filename = $c->config->{'forgot_password'}{'secret'}{'path'} .'/'. $c->config->{'forgot_password'}{'secret'}{'prefix'} . $secret; + my $timeout = 259200; # 3days in seconds + if ($c->config->{'forgot_password'}{'secret'}{'timeout'}) { + $timeout = $c->config->{'forgot_password'}{'secret'}{'timeout'}; + } + + # find secret + if (!$secret || !open(FILE, "<$filename")) { + # if secret is wrong, timeout expired? + return ''; + } + read(FILE, $email, 255); + close FILE; + + # check the time, and see if it's longer than timeout + my @s = stat($filename); + if (time() > $s[9] + $timeout) { + # expired + return ''; + } + + return $email; +} + +sub remove_secret { + my ($c, $secret) = @_; + my $filename = $c->config->{'forgot_password'}{'secret'}{'path'} .'/'. $c->config->{'forgot_password'}{'secret'}{'prefix'} . $secret; + unlink $filename; +} + +sub find_user_email { + my ($c, $email) = @_; + + # find user by email; + my $emailfilter = $c->config->{'authentication'}{'realms'}{'ldap'}{'store'}{'email_filter'}; + $emailfilter =~ s/\%s/$email/g, + $c->log->debug("Searching for email $email with filter $emailfilter"); + return $c->model('Proxy')->search($emailfilter); +} + + +=head1 AUTHOR + +Buchan Milne + +=head1 LICENSE + +This library is free software. You can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut + +__PACKAGE__->meta->make_immutable; + +1; diff --git a/root/email/forgot_password.tt b/root/email/forgot_password.tt new file mode 100644 index 0000000..4826955 --- /dev/null +++ b/root/email/forgot_password.tt @@ -0,0 +1,7 @@ +[% l('Dear [_1],',cn) %] +[% l('Your [_1] account has been requested to change the password. If you did not do this, or you do not want to change your password; you can just do nothing.',c.config.organisation) %] +[% l('To reset your password, please follow the link below.') %] +[% url %] + +-- +http://mageia.org/ diff --git a/root/forgot_password/check.tt b/root/forgot_password/check.tt new file mode 100644 index 0000000..b8ec933 --- /dev/null +++ b/root/forgot_password/check.tt @@ -0,0 +1,4 @@ +

Success

+

+[% message %] +

\ No newline at end of file diff --git a/root/forgot_password/complete.tt b/root/forgot_password/complete.tt new file mode 100644 index 0000000..3a9995c --- /dev/null +++ b/root/forgot_password/complete.tt @@ -0,0 +1,6 @@ +

[% l('Email sent.') %]

+ +

+ [% l('Operation was successful.') %] + [% l('Check your mail for password reset instructions.') %] +

diff --git a/root/forgot_password/confirm.tt b/root/forgot_password/confirm.tt new file mode 100644 index 0000000..3c3e07d --- /dev/null +++ b/root/forgot_password/confirm.tt @@ -0,0 +1,21 @@ + +

[% l('Enter new password.') %]

+ +
+
+ + + + + + + + + + +
+ + +
+
+
diff --git a/root/forgot_password/index.tt b/root/forgot_password/index.tt new file mode 100644 index 0000000..c5fbddd --- /dev/null +++ b/root/forgot_password/index.tt @@ -0,0 +1,17 @@ + +

[% l('Forgot your password?') %]

+ +
+
+ + + + + + + +
+ +
+
+
diff --git a/root/index.tt b/root/index.tt index 8e1720c..de256f6 100644 --- a/root/index.tt +++ b/root/index.tt @@ -14,8 +14,7 @@
[% l('Register') %] | - @todo [% l('Forgotten password?') %] - + [% l('Forgotten password?') %]
-- cgit v1.2.1