aboutsummaryrefslogtreecommitdiffstats
path: root/lib/CatDap/Controller
diff options
context:
space:
mode:
Diffstat (limited to 'lib/CatDap/Controller')
-rw-r--r--lib/CatDap/Controller/admin.pm28
-rw-r--r--lib/CatDap/Controller/forgot_password.pm283
-rw-r--r--lib/CatDap/Controller/register.pm4
-rw-r--r--lib/CatDap/Controller/user.pm19
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 {