diff options
Diffstat (limited to 'lib/CatDap/Controller')
-rw-r--r-- | lib/CatDap/Controller/admin.pm | 28 | ||||
-rw-r--r-- | lib/CatDap/Controller/forgot_password.pm | 283 | ||||
-rw-r--r-- | lib/CatDap/Controller/register.pm | 4 | ||||
-rw-r--r-- | lib/CatDap/Controller/user.pm | 19 |
4 files changed, 320 insertions, 14 deletions
diff --git a/lib/CatDap/Controller/admin.pm b/lib/CatDap/Controller/admin.pm index 914b2d0..7fd5539 100644 --- a/lib/CatDap/Controller/admin.pm +++ b/lib/CatDap/Controller/admin.pm @@ -143,7 +143,15 @@ sub account : Local { my $mesg = $c->model('user') ->search("(&(objectClass=inetOrgPerson)($attribute=$value))"); - my @entries = $mesg->entries; + my @orig_entries = $mesg->entries; + my @entries; + foreach my $entry (@orig_entries) { + my %new_entry; + foreach my $attr ($entry->attributes) { + $new_entry{$attr} = Encode::decode_utf8($entry->get_value($attr)); + } + push @entries, \%new_entry; + } push @errors, $mesg->error if $mesg->code; $c->stash( entries => \@entries ); $c->stash( errors => \@errors ); @@ -204,7 +212,16 @@ sub account_promote : Local { my $mesg = $c->model('user') ->search("(&(objectClass=inetOrgPerson)(!(objectClass=posixAccount)))"); - my @entries = $mesg->entries; + my @orig_entries = $mesg->entries; + my @entries; + foreach my $entry (@orig_entries) { + my %new_entry; + foreach my $attr($entry->attributes) { + $new_entry{$attr} = Encode::decode_utf8($entry->get_value($attr)); + } + push @entries, \%new_entry; + } + $c->stash( entries => \@entries ); push @errors, $mesg->error if $mesg->code; $mesg = $c->model('user')->search("(objectClass=posixGroup)"); @@ -295,6 +312,7 @@ sub account_modify : Local { if grep /$attr/, @{ ${ $c->config }{'Controller::User'}{'skip_attrs'} }; my @vals = $entry->get_value($attr); + foreach (@vals) { $_ = Encode::decode_utf8( $_ ); } $attrdef = $schema->attribute($attr) or die("getting schema failed: $!"); my %valhash = ( @@ -554,7 +572,7 @@ sub group : Local { my $attribute = $c->req->param('attribute'); $attribute =~ s/[^\w\d]//g; my $value = $c->req->param('value'); - $value =~ s/[^\w\d\*]//g; + $value =~ s/[^\w\d\* ]//g; my $mesg = $c->model('user') ->search("(&(objectclass=posixGroup)($attribute=$value))"); @@ -574,9 +592,9 @@ sub group_modify : Local { $c->stash( subpages => gensubpages('account') ); my @errors; $c->detach('/admin/group') if $group eq ''; - if ( $group !~ /^[\w\d]*$/ ) { + if ( $group !~ /^[\w\d ]*$/ ) { push @errors, "Group contains illegal characters"; - $c->detach('admin/group'); + $c->detach('/admin/group'); } my $mesg = $c->model('user')->search("(&(objectClass=posixGroup)(cn=$group))"); diff --git a/lib/CatDap/Controller/forgot_password.pm b/lib/CatDap/Controller/forgot_password.pm new file mode 100644 index 0000000..92792b5 --- /dev/null +++ b/lib/CatDap/Controller/forgot_password.pm @@ -0,0 +1,283 @@ +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->code) { + $c->log->info(printf("Search failed: %s"),$mesg->error); + push @errors, $c->loc('Error while searching for account: ') . $mesg->error; + } + my @entries = $mesg->entries; + if (@entries != 1) { + push @errors,$c->loc( + 'This email address is not bound to an account' + ); + } + my $checkfilter = '(&' . $c->config->{'forgot_password'}{'allow_filter'} . + $emailfilter . ')'; + $c->log->info(sprintf("Checking if user passes allow_filter $checkfilter")); + $mesg = $c->model('Proxy')->search($checkfilter); + if ($mesg->code) { + $c->log->info(printf("Search failed: %s"),$mesg->error); + push @errors, $c->loc('Error while searching for account: ') . $mesg->error; + + } + my @checkentries = $mesg->entries; + if (@entries == 1 and @checkentries != 1) { + push @errors,$c->loc( + 'Privileged accounts may not recover passwords via this mechanism' + ); + } + + if (@errors) { + $c->stash(errors => \@errors); + $c->stash(template => 'forgot_password/index.tt'); + return; + } + + my $secret = gen_secret($c, $email); + + $c->stash( + email => { + 'to' => $email, + 'from' => ${$c->config}{'emailfrom'}, + 'subject' => ${$c->config}{'apptitle'} . " - " . $c->loc('Forgotten password'), + 'template' => 'forgot_password.tt', + }, + url => $c->uri_for('/forgot_password/confirm') . "?secret=$secret", + cn => $entries[0]->cn, + ); + + $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 do not 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"; + } + + # 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/lib/CatDap/Controller/register.pm b/lib/CatDap/Controller/register.pm index a488181..d52971e 100644 --- a/lib/CatDap/Controller/register.pm +++ b/lib/CatDap/Controller/register.pm @@ -65,12 +65,12 @@ sub check : Local { push @errors, $c->loc('Username is not authorized to be used'); } - if ($c->request->params->{gn} !~ /^\p{IsAlnum}+$/) { + if ($c->request->params->{gn} !~ /^[\p{IsAlnum} ]+$/) { push @errors, $c->loc( 'The first name supplied contains illegal characters' ); } - if ($c->request->params->{sn} !~ /^\p{IsAlnum}+$/) { + if ($c->request->params->{sn} !~ /^[\p{IsAlnum} ]+$/) { push @errors, $c->loc( 'The surname supplied contains illegal characters' ); diff --git a/lib/CatDap/Controller/user.pm b/lib/CatDap/Controller/user.pm index b3edb32..f2171eb 100644 --- a/lib/CatDap/Controller/user.pm +++ b/lib/CatDap/Controller/user.pm @@ -202,6 +202,7 @@ sub index :Path :Args(0) { next if ($attr eq "objectClass"); next if grep /$attr/,@{${$c->config}{'Controller::User'}{'skip_attrs'}}; my @vals = $entry->get_value($attr); + foreach (@vals) { $_ = Encode::decode_utf8( $_ ); } $attrdef = $schema->attribute($attr) or die ("getting schema failed: $!"); my %valhash = ( name => $attr, @@ -222,6 +223,7 @@ sub index :Path :Args(0) { grep /$attrname/,@attributes or grep /$attrname/,@{${$c->config}{'Controller::User'}{'uneditable_attrs'}} or grep /$attrname/,@{${$c->config}{'Controller::User'}{'skip_attrs'}} or + grep /$attrname/,@{${$c->config}{'Controller::User'}{'editable_attrs'}} and push @may, $attrname; } } @@ -246,7 +248,7 @@ sub add : Local { my $entry = $mesg->entry; $entry->add( $attr => $value); $c->log->info("Adding $attr = $value to user $user"); - $entry->update; + $mesg = $entry->update; push @{${$c->stash}{'errors'}},$mesg->error if $mesg->code; $c->log->info($mesg->error); $c->res->redirect('/user'); @@ -257,16 +259,19 @@ sub delete : Local : Args(2) { my ($mesg,$entry,$user,$userfilter); $user = $c->user->username; $userfilter = $c->user->store->user_filter; - $userfilter =~ s/%s/$c->user->username/g; - $c->log->debug("Searching for user $user"); + $userfilter =~ s/%s/$user/g; + $c->log->debug("Searching for user $user with filter $userfilter"); $mesg = $c->model('User')->search($userfilter); + push @{${$c->stash}{'errors'}},$mesg->error if $mesg->code; + $c->log->info($mesg->error) if $mesg->code; $entry = $mesg->entry; - $c->log->info("Deleting $attrname = $attrvalue from user $user"); + $c->log->info("Deleting $attrname: $attrvalue from dn " . $entry->dn); $entry->delete($attrname => $attrvalue); - $entry->update; + $mesg = $entry->update; push @{${$c->stash}{'errors'}},$mesg->error if $mesg->code; - $c->log->info($mesg->error); - $c->res->redirect('/user'); + $c->log->info("Result of update: " . $mesg->error . "," . $mesg->code) if $mesg->code; + $c->res->redirect('/user') unless $mesg->code; + $c->stash({ attrname => $attrname, attrvalue => $attrvalue}); } sub password : Local { |