diff --git a/deps.nix b/deps.nix index 494136ab..b533262f 100644 --- a/deps.nix +++ b/deps.nix @@ -37,5 +37,7 @@ in perlPackages.DataDump perlPackages.JSONXS perlPackages.DateTime + perlPackages.DigestSHA1 + perlPackages.CryptRandPasswd nixPerl ] diff --git a/src/lib/Hydra/Controller/Admin.pm b/src/lib/Hydra/Controller/Admin.pm index 3a6d3fd0..8d1edfe2 100644 --- a/src/lib/Hydra/Controller/Admin.pm +++ b/src/lib/Hydra/Controller/Admin.pm @@ -7,6 +7,12 @@ use Hydra::Helper::Nix; use Hydra::Helper::CatalystUtils; use Hydra::Helper::AddBuilds; use Data::Dump qw(dump); +use Digest::SHA1 qw(sha1_hex); +use Crypt::RandPasswd; +use Sys::Hostname::Long; +use Email::Simple; +use Email::Sender::Simple qw(sendmail); +use Email::Sender::Transport::SMTP; sub nixMachines { my ($c) = @_; @@ -56,6 +62,111 @@ sub index : Chained('admin') PathPart('') Args(0) { } ) ]; $c->stash->{template} = 'admin.tt'; } + +sub updateUser { + my ($c, $user) = @_; + + my $username = trim $c->request->params->{"username"}; + my $fullname = trim $c->request->params->{"fullname"}; + my $emailaddress = trim $c->request->params->{"emailaddress"}; + my $emailonerror = trim $c->request->params->{"emailonerror"}; + my $roles = $c->request->params->{"roles"} ; + + error($c, "Invalid or empty username.") if $username eq ""; + + $user->update( + { username => $username + , maxconcurrent => $fullname + , emailaddress => $emailaddress + , emailonerror => $emailonerror + }); + $user->userroles->delete_all; + if(ref($roles) eq 'ARRAY') { + for my $s (@$roles) { + $user->userroles->create({ role => $s}) ; + } + } else { + $user->userroles->create({ role => $roles}) ; + } +} + +sub user : Chained('admin') PathPart('user') CaptureArgs(1) { + my ($self, $c, $username) = @_; + + requireAdmin($c); + + my $user = $c->model('DB::Users')->find($username) + or notFound($c, "User $username doesn't exist."); + + $c->stash->{user} = $user; +} + +sub users : Chained('admin') PathPart('users') Args(0) { + my ($self, $c) = @_; + $c->stash->{users} = [$c->model('DB::Users')->search({}, {order_by => "username"})]; + + $c->stash->{template} = 'users.tt'; +} + +sub user_edit : Chained('user') PathPart('edit') Args(0) { + my ($self, $c) = @_; + + $c->stash->{template} = 'user.tt'; + $c->stash->{edit} = 1; +} + +sub user_edit_submit : Chained('user') PathPart('submit') Args(0) { + my ($self, $c) = @_; + requirePost($c); + + txn_do($c->model('DB')->schema, sub { + updateUser($c, $c->stash->{user}) ; + }); + $c->res->redirect("/admin/users"); +} + +sub sendemail { + my ($to, $subject, $body) = @_; + + my $url = hostname_long; + my $sender = ($ENV{'USER'} || "hydra") . "@" . $url; + + my $email = Email::Simple->create( + header => [ + To => $to, + From => "Hydra <$sender>", + Subject => $subject + ], + body => $body + ); + + sendmail($email); +} + +sub reset_password : Chained('user') PathPart('reset-password') Args(0) { + my ($self, $c) = @_; + + # generate password + my $password = Crypt::RandPasswd->word(8,10); + + # calculate hash + my $hashed = sha1_hex($password); + + $c->stash->{user}-> update({ password => $hashed}) ; + + # send email + + sendemail( + $c->user->emailaddress, + "New password for Hydra", + "Hi,\n\n". + "Your password has been reset. Your new password is '$password'.\n". + "You can change your password at http://".hostname_long." .\n". + "With regards, Hydra\n" + ); + + $c->res->redirect("/admin/users"); +} sub machines : Chained('admin') PathPart('machines') Args(0) { my ($self, $c) = @_; diff --git a/src/lib/Hydra/Controller/Root.pm b/src/lib/Hydra/Controller/Root.pm index e7e7cf62..ae8e0e07 100644 --- a/src/lib/Hydra/Controller/Root.pm +++ b/src/lib/Hydra/Controller/Root.pm @@ -5,7 +5,7 @@ use warnings; use base 'Hydra::Base::Controller::ListBuilds'; use Hydra::Helper::Nix; use Hydra::Helper::CatalystUtils; - +use Digest::SHA1 qw(sha1_hex); # Put this controller at top-level. __PACKAGE__->config->{namespace} = ''; @@ -196,5 +196,29 @@ sub nar :Local :Args(1) { $c->stash->{storePath} = $path; } +sub change_password : Path('change-password') : Args(0) { + my ($self, $c) = @_; + + requireLogin($c) if !$c->user_exists; + + $c->stash->{template} = 'change-password.tt'; +} + +sub change_password_submit : Path('change-password/submit') : Args(0) { + my ($self, $c) = @_; + + requireLogin($c) if !$c->user_exists; + + my $password = $c->request->params->{"password"}; + my $password_check = $c->request->params->{"password_check"}; + print STDERR "$password \n"; + print STDERR "$password_check \n"; + error($c, "Passwords did not match, go back and try again!") if $password ne $password_check; + + my $hashed = sha1_hex($password); + $c->user->update({ password => $hashed}) ; + + $c->res->redirect("/"); +} 1; diff --git a/src/root/change-password.tt b/src/root/change-password.tt new file mode 100644 index 00000000..4a5c359c --- /dev/null +++ b/src/root/change-password.tt @@ -0,0 +1,23 @@ +[% WRAPPER layout.tt title="Change password" %] +[% PROCESS common.tt %] + +
+ +

Change password

+ + + + + + + + + + +
Enter password:
Enter password again:
+ +

+ +
+ +[% END %] diff --git a/src/root/navbar.tt b/src/root/navbar.tt index 025ecc19..8521b7f3 100644 --- a/src/root/navbar.tt +++ b/src/root/navbar.tt @@ -82,6 +82,9 @@ [% INCLUDE makeLink uri = c.uri_for(c.controller('Admin').action_for('managenews')) title = "News" %] + [% INCLUDE makeLink + uri = c.uri_for(c.controller('Admin').action_for('users')) + title = "Users" %] [% END %] [% END %] diff --git a/src/root/user.tt b/src/root/user.tt new file mode 100644 index 00000000..150bc01f --- /dev/null +++ b/src/root/user.tt @@ -0,0 +1,70 @@ +[% WRAPPER layout.tt title=(create ? "New user" : "Editing user '$user.username'") %] +[% PROCESS common.tt %] + +[% BLOCK roleoption %] + +[% END %] + +
+ +

User[% IF ! create %] '[% user.username %]'[% END %]

+ + + [% IF create %] + + + + + [% END %] + + + + + + + + + + + + + + + + +
Username:[% INCLUDE maybeEditString param="username" value=user.username %]
Full name:[% INCLUDE maybeEditString param="fullname" value=user.fullname %]
Email:[% INCLUDE maybeEditString param="emailaddress" value=user.emailaddress %]
Evaluation error notifications: + [% INCLUDE renderSelection param="emailonerror" curValue=user.emailonerror options={"1" = "Yes", "0" = "No"} %] +
Roles: + +
+ +

+ +
+ +[% IF !create %] + +
+

+
+ + +[% END %] + +[% END %] diff --git a/src/root/users.tt b/src/root/users.tt new file mode 100644 index 00000000..f75796a9 --- /dev/null +++ b/src/root/users.tt @@ -0,0 +1,34 @@ +[% WRAPPER layout.tt title="Users" %] +[% PROCESS common.tt %] + +

Users

+ + + + + + + + + + + + + + [% FOREACH u IN users %] + + + + + + + + + [% END %] + + +
UsernameNameEmailRolesEval. notificationsOptions
[% INCLUDE maybeLink uri = c.uri_for(c.controller('Admin').action_for('user_edit'), [u.username]) content = u.username %][% u.fullname %][% u.emailaddress %][% FOREACH r IN u.userroles %][% r.role %] [% END %][% IF u.emailonerror %]Yes[% ELSE %]No[% END %][ [% INCLUDE maybeLink uri = c.uri_for(c.controller('Admin').action_for('reset_password'), [u.username]) content = "Reset password" confirmmsg = "Are you sure you want to reset the password for this user?" %] ]
+ +

[ Add a new user ]

+ +[% END %]