2 package SGN
::Controller
::AJAX
::User
;
8 use CXGN
::People
::Roles
;
9 use CXGN
::BreedingProgram
;
11 BEGIN { extends
'Catalyst::Controller::REST' };
14 default => 'application/json',
16 map => { 'application/json' => 'JSON' },
20 sub login
: Path
('/ajax/user/login') Args
(0) {
24 my $username = $c->req->param("username");
25 my $password = $c->req->param("password");
26 my $goto_url = $c->req->param("goto_url");
28 $goto_url = $c->req->referer if $goto_url eq '/';
29 print STDERR
"Goto URL = $goto_url\n";
31 my $login = CXGN
::Login
->new($c->dbc->dbh());
32 my $login_info = $login->login_user($username, $password);
34 if (exists($login_info->{incorrect_password
}) && $login_info->{incorrect_password
} == 1) {
35 $c->stash->{rest
} = { error
=> "Login credentials are incorrect. Please try again." };
38 elsif (exists($login_info->{account_disabled
}) && $login_info->{account_disabled
}) {
39 $c->stash->{rest
} = { error
=> "This account has been disabled due to $login_info->{account_disabled}. Please contact the database to fix this problem." };
44 message
=> "Login successful",
50 sub logout
:Path
('/ajax/user/logout') Args
(0) {
54 my $login = CXGN
::Login
->new($c->dbc->dbh());
55 $login->logout_user();
57 $c->stash->{rest
} = { message
=> "User successfully logged out." };
60 sub new_account
:Path
('/ajax/user/new') Args
(0) {
63 my $sp_person_id = $c->user() ?
$c->user->get_object()->get_sp_person_id() : undef;
64 my $schema = $c->dbic_schema("Bio::Chado::Schema", undef, $sp_person_id);
66 print STDERR
"Adding new account...\n";
67 if ($c->config->{is_mirror
}) {
68 $c->stash->{template
} = '/system_message.mas';
69 $c->stash->{message
} = "This site is a mirror site and does not support adding users. Please go to the main site to create an account.";
74 my ($first_name, $last_name, $username, $password, $confirm_password, $email_address, $organization, $breeding_program_ids)
75 = map { $c->req->params->{$_} } (qw
|first_name last_name username password confirm_password email_address organization breeding_programs
|);
77 # Set organization from breeding programs, if provided
78 if ($breeding_program_ids && ref($breeding_program_ids) ne 'ARRAY') {
79 $breeding_program_ids = [$breeding_program_ids];
81 my @breeding_program_names;
82 if ( !$organization && $breeding_program_ids ) {
83 foreach my $breeding_program_id (@
$breeding_program_ids) {
84 my $breeding_program = CXGN
::BreedingProgram
->new({ schema
=> $schema , program_id
=> $breeding_program_id });
85 push(@breeding_program_names, $breeding_program->get_name());
87 $organization = join(', ', @breeding_program_names);
92 # check password properties...
95 if (length($username) < 7) {
96 push @fail, "Username is too short. Username must be 7 or more characters";
97 } elsif ( $username =~ /\s/ ) {
98 push @fail, "Username must not contain spaces";
100 # does user already exist?
102 my $existing_login = CXGN
::People
::Login
-> get_login
($c->dbc()->dbh(), $username);
104 if ($existing_login->get_username()) {
105 push @fail, "Username \"$username\" is already in use. Please pick a different username.";
109 if (length($password) < 7) {
110 push @fail, "Password is too short. Password must be 7 or more characters";
112 if ("$password" ne "$confirm_password") {
113 push @fail, "Password and confirm password do not match.";
116 if ($password eq $username) {
117 push @fail, "Password must not be the same as your username.";
119 if ($email_address !~ m/[^\@]+\@[^\@]+/) {
120 push @fail, "Email address is invalid.";
122 if ( $email_address ) {
123 my @person_ids = CXGN
::People
::Login
->get_login_by_email($c->dbc()->dbh(), $email_address);
124 if ( scalar(@person_ids) > 0 ) {
125 push @fail, "Email address is already associated with an account.";
128 unless($first_name) {
129 push @fail,"You must enter a first name or initial.";
132 push @fail,"You must enter a last name.";
136 $c->stash->{rest
} = { error
=> "Account creation failed for the following reason(s): ".(join ", ", @fail) };
141 my $confirm_code = $self->tempname();
142 my $new_user = CXGN
::People
::Login
->new($c->dbc->dbh());
143 $new_user -> set_username
($username);
144 $new_user -> set_pending_email
($email_address);
145 $new_user -> set_disabled
('unconfirmed account');
146 $new_user -> set_organization
($organization);
147 $new_user -> store
();
149 print STDERR
"Generated sp_person_id ".$new_user->get_sp_person_id()."\n";
150 print STDERR
"Update password and confirm code...\n";
151 $new_user->update_password($password);
152 $new_user->update_confirm_code($confirm_code);
154 print STDERR
"Store Person object...\n";
155 #this is being added because the person object still uses two different objects, despite the fact that we've merged the tables
156 my $person_id=$new_user->get_sp_person_id();
157 my $new_person=CXGN
::People
::Person
->new($c->dbc->dbh(),$person_id);
158 $new_person->set_first_name($first_name);
159 $new_person->set_last_name($last_name);
160 $new_person->store();
162 # Add user to breeding programs
163 if ( $c->config->{user_registration_join_breeding_programs
} ) {
164 my $person_roles = CXGN
::People
::Roles
->new({ bcs_schema
=> $schema });
165 my $sp_roles = $person_roles->get_sp_roles();
166 my %roles = map {$_->[0] => $_->[1]} @
$sp_roles;
167 foreach my $breeding_program_name (@breeding_program_names) {
168 my $role_id = $roles{$breeding_program_name};
170 my $error = $person_roles->add_sp_role($breeding_program_name);
172 print STDERR
"ERROR: Could not create role $breeding_program_name [$error]\n";
175 my $new_sp_roles = $person_roles->get_sp_roles();
176 my %new_roles = map {$_->[0] => $_->[1]} @
$new_sp_roles;
177 $role_id = $new_roles{$breeding_program_name};
181 my $add_role = $person_roles->add_sp_person_role($person_id, $role_id);
186 # Add additional user details for admin confirmation
187 my $user_details = '';
188 if ( $c->config->{user_registration_admin_confirmation
} && $c->config->{user_registration_admin_confirmation_email
} ) {
189 $user_details .= "Name: $first_name $last_name\n";
190 $user_details .= "Email: $email_address\n";
191 $user_details .= "Organization(s): $organization\n";
194 my $host = $c->config->{main_production_site_url
};
195 my $project_name = $c->config->{project_name
};
196 my $subject="[$project_name] Email Address Confirmation Request";
197 my $body=<<END_HEREDOC;
199 This message is sent to confirm the email address for community user
203 Please click (or cut and paste into your browser) the following link to
204 confirm your account and email address:
206 $host/user/confirm?username=$username&confirm_code=$confirm_code
211 Please do *NOT* reply to this message. If you have any trouble confirming your
212 email address or have any other questions, please use the contact form instead:
217 # Send confirmation email to admin
219 if ( $c->config->{user_registration_admin_confirmation} && $c->config->{user_registration_admin_confirmation_email} ) {
220 CXGN::Contact::send_email($subject,$body,'user_registration_admin_confirmation_email');
221 $message = "Your account has been created but first must be confirmed by the site administrators. You will receive an email once your account has been confirmed.";
224 # Send confirmation email to user
226 CXGN::Contact::send_email($subject,$body,$email_address);
227 $message = "To continue, you must confirm that we can reach you via email address \"$email_address\". An email has been sent with a URL to confirm this address. Please check your email for this message and use the link to confirm your email address.";
230 $c->stash->{rest} = {
231 message => "Account was created with username \"$username\".\n\n$message\n\nYou will be able to login once your account has been confirmed."
236 sub change_account_info_action :Path('/ajax/user/update') Args(0) {
241 $c->stash->{rest} = { error => "You must be logged in to use this page." };
245 my $person = new CXGN::People::Login($c->dbc->dbh(), $c->user->get_sp_person_id());
247 # my ($current_password, $change_username, $change_password, $change_email) = $c->req->param({qw(current_password change_username change_password change_email)});
249 my $args = $c->req->params();
251 if (!$args->{change_password
} && ! $args->{change_username
} && !$args->{change_email
}) {
252 my $error = "No actions were requested. Please select which fields you would like to update by checking the appropriate checkbox(es) on the form and entering your new information.";
254 $c->stash->{rest
} = { error
=> $error };
258 chomp($args->{current_password
});
259 if (! $person->verify_password($args->{current_password
})) {
260 my $error = "Your current password does not match SGN records.";
262 $c->stash->{rest
} = { error
=> "$error" };
266 # Check for error conditions in all changes, before making any of them.
267 # Otherwise, we could end up making some changes and then failing on later
268 # ones. The user would then push the back button and their information may
269 # be different now but they will probably assume no changes were made. This
270 # is most troublesome if the current password changes.
272 if ($args->{change_username
}) {
273 #unless change_username is set, new_username won't be in the args hash because of the prestore test
274 my $new_username = $args->{new_username
};
275 if(length($new_username) < 7) {
276 my $error = "Username must be at least 7 characters long.";
278 $c->stash->{rest
} = { error
=> $error };
282 my $other_user = CXGN
::People
::Login
->get_login($c->dbc->dbh(), $new_username);
283 if (defined $other_user->get_sp_person_id() &&
284 ($person -> get_sp_person_id
() != $other_user->get_sp_person_id())) {
285 print STDERR
"Username alread in use.\n";
286 $c->stash->{rest
} = { error
=> "Username \"$new_username\" is already in use. Please select a different username." };
290 $person->set_username($new_username);
294 if ($args->{change_password
}) {
295 #unless change_password is set, new_password won't be in the args hash because of the prestore test
296 my ($new_password, $confirm_password) = ($args->{new_password
}, $args->{confirm_password
});
297 if(length($args->{new_password
}) < 7) {
298 print STDERR
"Password too short\n";
299 $c->stash->{rest
} = { error
=> "Passwords must be at least 7 characters long. Please try again." };
303 if($args->{new_password
} !~ /^[a-zA-Z0-9~!@#$^&*_.=:;<>?]+$/) {
304 print STDERR
"Illegal characters in password\n";
305 $c->stash->{rest
} = { error
=> "An error occurred. Please use your browser's back button to try again.. The Password can't contain spaces or these symbols: <u><b>` ( ) [ ] { } - + ' \" / \\ , |</b></u>." };
308 if($args->{new_password
} ne $args->{confirm_password
}) {
309 print STDERR
"Password don't match.\n";
310 $c->stash->{rest
} = { error
=> "New password entries do not match. You must enter your new password twice to verify accuracy." };
314 print STDERR
"Saving new password to the database\n";
315 $person->update_password($args->{new_password
});
318 my $user_private_email = $c->user->get_private_email();
319 if($args->{change_email
}) {
320 #unless change_email is set, private_email won't be in the args hash because of the prestore test
321 my ($private_email, $confirm_email) = ($args->{private_email
}, $args->{confirm_email
});
322 if($private_email !~ m/^[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.-]+$/) {
323 print STDERR
"Invalid email address\n";
324 $c->stash->{rest
} = { error
=> "An error occurred. Please use your browser's back button to try again. The E-mail address \"$private_email\" does not appear to be a valid e-mail address." };
327 if($private_email ne $confirm_email) {
328 print STDERR
"Emails don't match\n";
329 $c->stash->{rest
} = { error
=> "An error occurred. Please use your browser's back button to try again. New e-mail address entries do not match. You must enter your new e-mail address twice to verify accuracy." };
333 print STDERR
"Saving private email '$private_email' to the database\n";
334 $person->set_private_email($private_email);
335 my $confirm_code = $self->tempname();
336 $person->set_confirm_code($confirm_code);
339 $user_private_email = $private_email;
341 $self->send_confirmation_email($args->{username
}, $user_private_email, $confirm_code, $c->config->{main_production_site_url
});
345 $c->stash->{rest
} = { message
=> "Update successful" };
349 sub send_confirmation_email
{
350 my ($self, $username, $private_email, $confirm_code, $host) = @_;
351 my $subject = "[SGN] E-mail Address Confirmation Request";
353 my $body = <<END_HEREDOC;
355 You requested an account on the site $host.
357 Please do *NOT* reply to this message. The return address is not valid.
358 Use the contact form at $host/contact/form instead.
360 This message is sent to confirm the private e-mail address for community user
363 Please click (or cut and paste into your browser) the following link to
364 confirm your account and e-mail address:
366 $host/user/confirm?username=$username&confirm=$confirm_code
372 CXGN::Contact::send_email($subject, $body, $private_email);
376 sub forgot_username : Path('/ajax/user/forgot_username') Args(0) {
380 my $email = $c->req->param('forgot_username_email');
381 my @person_ids = CXGN::People::Login->get_login_by_email($c->dbc->dbh(), $email);
382 $self->send_forgot_username_email_message($c, $email, \@person_ids);
384 $c->stash->{rest} = {
385 message => "Username email sent. Please check your email for a message containing the username(s) of any accounts associated with your email address."
390 sub reset_password :Path('/ajax/user/reset_password') Args(0) {
394 my $email = $c->req->param('password_reset_email');
396 my @person_ids = CXGN::People::Login->get_login_by_email($c->dbc->dbh(), $email);
399 $c->stash->{rest} = { error => "The provided email ($email) is not associated with any account." };
403 if (@person_ids > 1) {
404 $c->stash->{rest} = { message => "The provided email ($email) is associated with multiple accounts. An email is sent for each account. Please notify the database team using the contact form to consolidate the accounts." };
409 foreach my $pid (@person_ids) {
410 my $email_reset_token = $self->tempname();
411 my $reset_link = $c->config->{main_production_site_url}."/user/reset_password_form?reset_password_token=$email_reset_token";
412 my $person = CXGN::People::Login->new( $c->dbc->dbh(), $pid);
413 $person->update_confirm_code($email_reset_token);
414 print STDERR "Sending reset link $reset_link\n";
415 $self->send_reset_email_message($c, $pid, $email, $reset_link, $person->{username});
416 push @reset_links, $reset_link;
417 push @reset_tokens, $email_reset_token;
420 $c->stash->{rest} = {
421 message => "Reset link sent. Please check your email and click on the link.",
422 reset_links => \@reset_links,
423 reset_tokens => \@reset_tokens
427 sub process_reset_password_form :Path('/ajax/user/process_reset_password') Args(0) {
431 my $token = $c->req->param("token");
432 my $confirm_password = $c->req->param("confirm_password");
433 my $new_password = $c->req->param("new_password");
435 if (length($new_password) < 7) {
436 $c->stash->{rest} = { error => "Password is too short. Password must be 7 or more characters" };
440 if ($confirm_password ne $new_password){
441 $c->stash->{rest} = { error => "Please enter the same password in the confirm password field!" };
446 my $q = "SELECT sp_person_id FROM sgn_people.sp_person WHERE confirm_code=?;";
447 my $h = $c->dbc->dbh()->prepare($q);
449 my ($person_id) = $h->fetchrow_array();
450 my $login = CXGN::People::Login->new($c->dbc->dbh(), $person_id);
451 $login->update_password($new_password);
452 $login->update_confirm_code("");
455 $c->stash->{rest} = { error => $@ };
458 $c->stash->{rest} = { message => "The password was successfully updated." };
463 sub send_forgot_username_email_message {
467 my $person_ids = shift;
470 foreach my $pid (@$person_ids) {
471 my $person = CXGN::People::Login->new( $c->dbc->dbh(), $pid);
472 push(@usernames, $person->get_username());
474 my $username_message = @usernames > 0 ? "The following username(s) are associated with your email address: " . join(', ', @usernames)
475 : "There are no accounts associated with your email address.";
477 my $project_name = $c->config->{project_name};
478 my $subject = "[$project_name] Forgot Username Request";
479 my $main_url = $c->config->{main_production_site_url};
481 my $body = <<END_HEREDOC
;
485 You have requested the username
(s
) for accounts associated with this email address on
$main_url.
489 If this request did
not come from you
, please let us know
.
491 To contact us
, please
do NOT reply to this message
; rather
, use the contact form
($main_url/contact/form
) instead
.
495 Your friends at
$project_name
499 CXGN
::Contact
::send_email
($subject, $body, $email);
502 sub send_reset_email_message
{
506 my $private_email = shift;
507 my $reset_link = shift;
510 my $project_name = $c->config->{project_name
};
511 my $subject = "[$project_name] Password Reset Request";
512 my $main_url = $c->config->{main_production_site_url
};
514 my $body = <<END_HEREDOC;
518 The user $person has requested a password reset on $main_url.
520 If this request did not come from you, please let us know.
522 To contact us, please do NOT reply to this message; rather, use the contact form ($main_url/contact/form) instead.
524 Your password can be reset using the following link, which you can either click or cut and paste into your browser:
530 Your friends at $main_url
534 CXGN::Contact::send_email($subject, $body, $private_email);
539 my $rand_string = "";
540 my $dev_urandom = new IO::File "</dev/urandom" || print STDERR "Can't open /dev/urandom";
541 $dev_urandom->read( $rand_string, 16 );
542 my @bytes = unpack( "C16", $rand_string );
547 $rand_string .= chr( 65 + $_ );
550 $rand_string .= chr( 97 + ( $_ - 26 ) );
553 $rand_string .= chr( 48 + ( $_ - 52 ) );
559 sub get_login_button_html :Path('/ajax/user/login_button_html') Args(0) {
563 my $production_site = $c->config->{main_production_site_url};
565 # if the site is a mirror, gray out the login/logout links
566 if( $c->config->{'is_mirror'} ) {
567 print STDERR "generating login button for mirror site...\n";
569 <a style="line-height: 1.2; text-decoration: underline; background: none" href="$production_site" title="log in on main site">main site</a>
570 } elsif ( $c->config->{disable_login} ) {
571 <li class="dropdown">
572 <div class="btn-group" role="group" aria-label="..." style="height:34px; margin: 1px 0px 0px 0px" >
573 <button class="btn btn-primary disabled" type="button" style="margin: 7px 7px 0px 0px">Login</button>
579 } elsif ( $c->req->uri->path_query =~ "logout=yes") {
580 print STDERR
"generating login button for logout...\n";
582 <li class="dropdown">
583 <div class="btn-group" role="group" aria-label="..." style="height:34px; margin: 1px 0px 0px 0px" >
584 <a href="/user/login">
585 <button class="btn btn-primary" type="button" style="margin: 7px 7px 0px 0px">Login</button>
591 } elsif ( $c->user_exists ) {
592 print STDERR
"Generate login button for logged in user...\n";
593 my $sp_person_id = $c->user() ?
$c->user->get_object()->get_sp_person_id() : undef;
594 my $username = $c->user->get_username();
596 <div class="btn-group" role="group" aria-label="..." style="height:34px; margin: 1px 3px 0px 0px">
597 <button id="navbar_profile" class="btn btn-primary" type="button" onclick='location.href="/solpeople/profile/$sp_person_id"' style="margin: 7px 0px 0px 0px" title="My Profile">$username</button>
598 <button id="navbar_lists" name="lists_link" class="btn btn-info" style="margin:7px 0px 0px 0px" type="button" title="Lists" onClick="show_lists();">
599 Lists <span class="glyphicon glyphicon-list-alt" ></span></button>
600 <button id="navbar_datasets" name="lists_link" class="btn btn-info" style="margin:7px 0px 0px 0px" type="button" title="Datasets" onClick="window.location='/search/datasets';">
601 <span class="glyphicon glyphicon-list-alt" ></span> <span class="hidden-sm">Datasets</span></button>
602 <button id="navbar_personal_calendar" name="personal_calendar_link" class="btn btn-primary" style="margin:7px 0px 0px 0px" type="button" title="Your Calendar">Calendar <span class="glyphicon glyphicon-calendar" ></span></button>
603 <button id="navbar_logout" class="btn btn-default glyphicon glyphicon-log-out" style="margin:6px 0px 0px 0px" type="button" onclick="logout();" title="Logout"></button>
608 print STDERR
"generating regular login button..\n";
610 <li
class="dropdown">
611 <div
class="btn-group" role
="group" aria
-label
="..." style
="height:34px; margin: 1px 0px 0px 0px" >
612 <button id
="site_login_button" name
="site_login_button" class="btn btn-primary" type
="button" style
="margin: 7px 7px 0px 0px; position-absolute: 10,10,100,10">Login
</button
>
619 print STDERR
"ERROR: $@\n";
620 $c->stash->{rest
} = { error
=> $@
};
622 return $c->stash->{rest
} = { html
=> $html, logged_in
=> $c->user_exists };
626 sub quick_create_user
:Path
('/ajax/user/quick_create_account') Args
(0) {
631 $c->stash->{rest
} = { error
=> "Need to be logged in to use feature." };
635 if (!$c->user()->check_roles("curator")) {
636 $c->stash->{rest
} = { error
=> "You don't have the privileges to use this feature" };
639 my $logged_in_person_id = $c->user()->get_sp_person_id();
641 my $logged_in_user=CXGN
::People
::Person
->new($c->dbc->dbh(), $logged_in_person_id);
642 $logged_in_person_id=$logged_in_user->get_sp_person_id();
643 my $logged_in_username=$logged_in_user->get_first_name()." ".$logged_in_user->get_last_name();
644 my $logged_in_user_type=$logged_in_user->get_user_type();
646 my ($username, $password, $confirm_password, $email_address, $new_user_type, $first_name, $last_name) =
647 map { print STDERR
$_." ".$c->req->param($_)."\n"; $c->req->param($_) } qw
| username password confirm_password confirm_email user_type first_name last_name
|;
649 print STDERR
"$username, $password, $confirm_password, $email_address, $new_user_type, $first_name, $last_name\n";
651 my $new_user_login=CXGN
::People
::Login
->new($c->dbc->dbh);
656 if(length($username)<7){push @fail,"Username is too short. Username must be 7 or more characters";}
657 my $existing_login=CXGN
::People
::Login
->get_login($c->dbc->dbh, $username);
659 if($existing_login->get_username()){push @fail,"Username \"$username\" is already in use. Please pick a different us
662 if(length($password)<7){push @fail,"Password is too short. Password must be 7 or more characters";}
664 if("$password" ne "$confirm_password"){push @fail,"Password and confirm password do not match.";}
666 if($password eq $username){push @fail,"Password must not be the same as your username.";}
668 if($new_user_type ne 'user' and $new_user_type ne 'sequencer' and $new_user_type ne 'submitter'){
669 push @fail,"Sorry, but you cannot create user of type \"$new_user_type\" with web interface.";}
675 $fail_str .= "<li>$_</li>\n"
677 $c->stash->{rest
} = { error
=> $fail_str };
684 $new_user_login->set_username(encode_entities
($username));
685 $new_user_login->set_password($password);
686 $new_user_login->set_private_email(encode_entities
($email_address));
687 $new_user_login->set_user_type(encode_entities
($new_user_type));
688 $new_user_login->store();
689 my $new_user_person_id=$new_user_login->get_sp_person_id();
690 my $new_user_person=CXGN
::People
::Person
->new($c->dbc->dbh, $new_user_person_id);
691 $new_user_person->set_first_name(encode_entities
($first_name));
692 $new_user_person->set_last_name(encode_entities
($last_name));
693 ##removed. This was causing problems with creating new accounts for people,
694 ##and then not finding it in the people search.
695 #$new_user_person->set_censor(1);#censor by default, since we are creating this account, not the person whose info might be displayed, and they might not want it to be displayed
696 $new_user_person->store();
700 $c->stash->{rest
} = { html
=> "An error occurred. $@" };
703 $c->stash->{rest
} = { html
=> "<center><h4>Account successfully created for $first_name $last_name</h4><a href=\"/user/admin/quick_create_account\">Create another account" };