Commit b1e4cb34 authored by IKEDA Soji's avatar IKEDA Soji
Browse files

Issue #25: Missing Function "action_change_email".

- Restroing this function as new request handler "move_user".
- New scenarios move_user.* are added so that this function may either need
  confirmation (auth), be restricted to listmaster (listmaster) or disabled
  (closed).
parent b92077e8
......@@ -114,6 +114,9 @@ nobase_default_DATA = \
scenari/invite.owner \
scenari/invite.private \
scenari/invite.public \
scenari/move_user.auth \
scenari/move_user.closed \
scenari/move_user.listmaster \
scenari/remind.listmaster \
scenari/remind.listmasterdkim \
scenari/remind.owner \
......
......@@ -96,6 +96,12 @@
[%|loc()%]The 'invite' feature is restricted to list subscribers.[%END%]
[%|loc()%]If you are subscribed to the list with a different email address, you should either use that other email address or update your list membership with the new email address.[%END%]
[%~ ELSIF reason == 'move_user_closed' ~%]
[%|loc()%]The 'move_user' feature is unavailable.[%END%]
[%~ ELSIF reason == 'move_user_listmaster' ~%]
[%|loc()%]Changing user's email is not allowed.[%END%]
[%~ ELSIF report_entry == 'remind_listmaster' ~%]
[%|loc()%]The 'remind' feature is restricted to listmaster.[%END%]
......@@ -298,7 +304,11 @@
[%|loc%]Your request has been forwarded to the list owner[%END%]
[%~ ELSIF report_entry == 'sent_to_user' ~%]
[%|loc%]To confirm your identity and prevent anyone from subscribing you to this list against your will, a message containing an validation link was sent to the e-mail address you provided.[%END%]
[% IF report_param.email ~%]
[%|loc(report_param.email)%]Your request has been forwarded to the user %1[%END%]
[%~ ELSE ~%]
[%|loc%]To confirm your identity and prevent anyone from subscribing you to this list against your will, a message containing an validation link was sent to the e-mail address you provided.[%END%]
[%~ END %]
[%~###~%]
[%~ ELSIF report_entry == 'sent_to_user' ~%]
......@@ -766,12 +776,21 @@ Warning: this message may already have been sent by one of the list's editor.[%E
[%~ ELSIF report_entry == 'invalid_filename' ~%]
[%|loc(report_param.filename)%]Invalid filename: '%1'[%END%]
[%~ ELSIF report_entry == 'change_member_email_failed' ~%]
[%|loc(report_param.email,report_param.listname)%]Failed to update member email '%1' in list '%2'.[%END%]
[%~ ELSIF report_entry == 'change_member_email_failed_included' ~%]
[%|loc(report_param.listname)%]Failed to update member email in list '%1', list owner has been notified.[%END%]
[%~ ELSIF report_entry == 'change_member_email_failed_deleting' ~%]
[%|loc(report_param.email,report_param.listname)%]Failed to delete member email '%1' in list '%2'.[%END%]
[%~ ELSIF report_entry == 'change_admin_email_failed_included' ~%]
[%|loc(report_param.listname)%]Failed to update admin email in list '%1', list owner has been notified.[%END%]
[%~ ELSIF report_entry == 'no_email_changed' ~%]
[%|loc%]Address was not changed.[%END%]
[%~ ELSIF report_entry == 'merge_failed' ~%]
[%|loc(report_param.error)%]Your message cannot be personalized due to error: %1. Please check template syntax.[%END%]
......
......@@ -34,13 +34,6 @@ Subject: [% FILTER qencode %][%|loc(list.name)%]Management of list %1[%END%][%EN
[%|loc%]The list homepage:[%END%] [% 'info' | url_abs([list.name]) %]
[%|loc%]Owner and moderator guide:[%END%] [% 'help/admin' | url_abs %]
[% ELSIF type == 'ticket_to_send' -%]
Subject: [% FILTER qencode %][%|loc%]Changing your subscriber email address[%END%][%END%]
[%|loc%]You have requested to change your subscriber email address. To confirm your new email address with the SYMPA server, please click the link below:[%END%]
[% 'ticket' | url_abs([one_time_ticket]) %]
[% ELSIF type == 'ticket_to_family_signoff' -%]
Subject: [% FILTER qencode %][%|loc(family)%]Unsubscribing from family %1[%END%][%END%]
[% IF context == 'family_signoff'%]
......
title.gettext need authentication
equal([sender],[current_email]) smtp,dkim,md5,smime -> request_auth([email])
is_listmaster([sender]) md5,smime -> do_it
equal([sender],[email]) md5,smime -> do_it
true() smtp,dkim,md5,smime -> reject
title.gettext impossible
true() smtp,dkim,md5,smime -> reject(reason='move_user_closed')
title.gettext listmaster only
is_listmaster([sender]) md5,smime -> do_it
true() smtp,dkim,md5,smime -> reject(reason='move_user_listmaster')
......@@ -106,6 +106,17 @@
<p><strong>
[%|loc(shared_doc.name)%]Do you really want to delete %1?[%END%]
</strong></p>
[%~ ELSIF confirm_action == 'move_user' ~%]
<h2><i class="fa fa-check-circle"></i>
[%|loc%]Changing user's email[%END%]
</h2>
<p><strong>
[% IF user.email == current_email ~%]
[%|loc(email)%]Are you sure you wish to change your email to %1?[%END%]
[%~ ELSE ~%]
[%|loc(current_email,email)%]Are you sure you wish to change a user's email %1 to %2?[%END%]
[%~ END %]
</strong></p>
[%~ ELSIF confirm_action == 'remind' ~%]
<h2><i class="fa fa-check-circle"></i>
[%|loc%]Remind all subscribers[%END%]
......@@ -244,6 +255,14 @@
<input type="hidden" name="d_admin" value="[% d_admin %]" />
[%~ ELSIF confirm_action == 'd_delete' ~%]
<input type="hidden" name="path" value="[% shared_doc.paths.join("/") %]" />
[%~ ELSIF confirm_action == 'move_user' ~%]
<input type="hidden" name="current_email" value="[% current_email %]" />
<input type="hidden" name="email" value="[% email %]" />
<div>
[%|loc%]Changing your email address is a sensitive operation so we need to verify your email.[%END%]
[%|loc(email)%]To this end we will send you an email to this address: %1 with a validation link.[%END%]
[%|loc%]You shoukd check your mailbox now.[%END%]
</div>
[%~ ELSIF confirm_action == 'remove_arc' ~%]
<input type="hidden" name="yyyy" value="[% yyyy %]" />
<input type="hidden" name="month" value="[% month %]" />
......
......@@ -39,8 +39,13 @@
<p>[%|loc%]You can update your email address for all your list memberships at once. If you are also list owner or list moderator your email address for these roles will also be updated.[%END%]</p>
<form action="[% path_cgi %]" method="post">
<fieldset>
<label for="new_email">[%|loc%]New email address:[%END%] </label><input id="new_email" name="new_email" size="25" />
<input class="MainMenuLinks" type="submit" name="action_change_email_request" value="[%|loc%]Change Email[%END%]" />
<div>
<label for="new_email">[%|loc%]New email address:[%END%] </label>
<input id="new_email" name="email" size="25" />
</div>
<input type="hidden" name="current_email" value="[% user.email %] />
<input class="MainMenuLinks" type="submit" name="action_move_user"
value="[%|loc%]Change Email[%END%]" />
</fieldset>
</form>
<br />
......
......@@ -66,6 +66,27 @@
</fieldset>
</form>
<hr>
<h2>[%|loc%]Changing user's email[%END%]</h2>
<p>[%|loc%]You can update a user's email address for all their list memberships at once. If they are also list owner or list moderator, their email address for these roles will also be updated.[%END%]</p>
<form class="bold_label" action="[% path_cgi %]" method="post">
<fieldset>
<div>
<label for="current_email">[%|loc%]Current user email address: [%END%]
</label>
<input id="current_email" type="text" name="current_email" size="30" />
</div>
<div>
<label for="email">[%|loc%]New user email address: [%END%]</label>
<input id="email" type="text" name="email" size="30" />
</div>
<input type="hidden" name="action" value="move_user" />
<input type="hidden" name="previous_action" value="serveradmin" />
<input class="MainMenuLinks" type="submit" name="action_move_user"
value="[%|loc%]Update user's email[%END%]" />
</fieldset>
</form>
<hr>
<h2>[%|loc%]Impersonate another User[%END%]</h2>
<p>[%|loc%]Listmasters can switch context (impersonate) other users; this may be useful when providing assistance or when testing privileges. Enter the email address of the user you'd like to switch context to:[%END%]</p>
......
......@@ -291,8 +291,7 @@ our %comm = (
'dump_scenario' => 'do_dump_scenario',
'dump' => 'do_dump',
'remind' => 'do_remind',
'change_email' => 'do_change_email',
'change_email_request' => 'do_change_email_request',
'move_user' => 'do_move_user',
'load_cert' => 'do_load_cert',
'compose_mail' => 'do_compose_mail',
'send_mail' => 'do_send_mail',
......@@ -343,11 +342,13 @@ our %comm = (
);
 
my %comm_aliases = (
'add_fromsub' => 'auth_add',
'add_request' => 'import',
'del_fromsig' => 'auth_del',
'sigrequest' => 'signoff',
'subrequest' => 'subscribe',
'add_fromsub' => 'auth_add',
'add_request' => 'import',
'change_email' => 'move_user',
'change_email_request' => 'move_user',
'del_fromsig' => 'auth_del',
'sigrequest' => 'signoff',
'subrequest' => 'subscribe',
);
 
my %auth_action = (
......@@ -480,7 +481,7 @@ our %action_args = (
'request_topic' => ['list', 'authkey'],
'tag_topic_by_sender' => ['list'],
'ticket' => ['ticket'],
'change_email' => ['email'],
'move_user' => [],
'manage_template' => ['subaction', 'list', 'message_template'],
'rt_delete' => ['list', 'message_template'],
'rt_edit' => ['list', 'message_template'],
......@@ -530,8 +531,7 @@ our %required_args = (
'automatic_lists' => ['family'],
'attach' => ['param.list'],
'blacklist' => ['param.list'],
'change_email' => ['param.user.email'],
'change_email_request' => ['param.user.email', 'new_email'],
'move_user' => ['param.user.email', 'current_email|old_email', 'email|new_email'],
'close_list' => ['param.user.email', 'param.list'],
'close_list_request' => ['param.user.email', 'param.list'],
'compose_mail' => ['param.user.email', 'param.list'],
......@@ -904,6 +904,7 @@ our %in_regexp = (
'family' => Sympa::Regexps::family_name(),
 
# Email addresses
'current_email' => Sympa::Regexps::email(),
'email' => Sympa::Regexps::email() . '|' . Sympa::Regexps::uid(),
'init_email' => Sympa::Regexps::email(),
'old_email' => Sympa::Regexps::email(),
......@@ -1000,7 +1001,7 @@ my %filtering = (
## edit_list_request
#'edit_list' => {'*param*' => 'unescape_html'},
## Remove leading/trailing white spaces and lowercase
'change_email' => {'*email' => 'normalize'},
#XXX'move_user' => {'*email' => 'normalize'},
);
 
## Set locale configuration
......@@ -14379,281 +14380,77 @@ sub do_delete_pictures {
}
}
 
####################################################
# do_change_email_request
####################################################
# Checks a user's new email address and passes it
# to 'change_email'
#
# IN : -
#
# OUT : '1' | 'change_email'
#
####################################################
## Checks a users new email address by sending a ticket to the new email
## address
## and demanding that they click it to verify. Leads to 'change_email'
sub do_change_email_request {
wwslog('info', '(%s)', $in{'new_email'});
$param->{'new_email'} = $in{'new_email'};
Sympa::send_notify_to_user($robot, 'ticket_to_send', $in{'new_email'},
{email => $param->{'user'}{'email'}, ip => $ip})
or return undef;
return '1';
}
# No longer used: use do_move_user().
#sub do_change_email_request;
 
####################################################
# do_change_email
####################################################
# Changes a user's email address in Sympa environment
#
# IN : -
#
# OUT : '1' | 'pref' | undef
#
####################################################
## Change a user's email address in Sympa environment
sub do_change_email {
wwslog('info', '(%s)', $in{'email'});
# No longer used: use do_move_user().
#sub do_change_email;
 
my ($old_email, $new_email);
my $edited_by_listmaster;
## Changes a user's email address in Sympa environment
sub do_move_user {
wwslog('info', '(%s, %s)', $in{'current_email'}, $in{'email'});
 
unless ($in{'email'} || ($in{'old_email'} && $in{'new_email'})) {
Sympa::Report::reject_report_web('user', 'Missing argument',
{}, $param->{'action'});
wwslog('err',
"Lacking parameter : $in{'email'} or $in{'old_email'} or $in{'new_email'} "
);
web_db_log(
{ 'parameters' => $in{'email'},
$in{'old_email'}, $in{'new_email'},
'status' => 'error',
'error_type' => 'user'
}
);
my ($current_email, $email);
if ($in{'old_email'} and $in{'new_email'}) {
# Compatibility to 6.1.x or earlier.
$current_email = Sympa::Tools::Text::canonic_email($in{'old_email'});
$email = Sympa::Tools::Text::canonic_email($in{'new_email'});
} elsif ($in{'current_email'} and $in{'email'}) {
$current_email =
Sympa::Tools::Text::canonic_email($in{'current_email'});
$email = Sympa::Tools::Text::canonic_email($in{'email'});
}
$param->{'current_email'} = $current_email;
$param->{'email'} = $email;
 
## There are two ways to access this function 'change_email'. One from
## the preferences page and one from the serveradmin page
## If the process comes from server admin it needs the variables
## 'old_email' and 'new_email'.
if ($in{'old_email'} && $in{'new_email'}) {
## if variables old_email and new_email are present
## $edited_by_listmaster is set to one
## so that at the end of the function we can return to the SympaAdmin
## page
## instead of the preferences page
$edited_by_listmaster = 1;
unless (Sympa::is_listmaster($robot, $param->{'user'}{'email'})) {
Sympa::Report::reject_report_web('auth', 'User is not Listmaster',
{}, $param->{'action'});
wwslog('err', 'Not listmaster');
web_db_log(
{ 'parameters' => $in{'email'},
'status' => 'error',
'error_type' => 'authorization'
}
);
return undef;
}
$old_email = $in{'old_email'};
$new_email = $in{'new_email'};
} else {
$old_email = $in{'email'};
$new_email = $param->{'user'}{'email'};
unless (Sympa::Tools::Text::valid_email($current_email)
and Sympa::Tools::Text::valid_email($email)) {
return $in{'previous_action'} || 'pref';
}
 
my ($password, $newuser);
if ($newuser = Sympa::User::get_global_user($old_email)) {
$password = $newuser->{'password'};
}
# Action confirmed?
my $next_action = $session->confirm_action(
$param->{'action'}, $in{'response_action'},
arg => "$current_email,$email",
previous_action => ($in{'previous_action'} || 'pref'),
);
return $next_action unless $next_action eq '1';
 
## Do the change_email
my ($status, $failed_for) = Sympa::Admin::change_user_email(
current_email => $old_email,
new_email => $new_email,
robot => $robot
# Do the move_user
my $spindle = Sympa::Spindle::ProcessRequest->new(
context => $robot,
action => 'move_user',
current_email => $current_email,
email => $email,
sender => $param->{'user'}{'email'},
md5_check => 1,
scenario_context => {
sender => $param->{'user'}{'email'},
remote_host => $param->{'remote_host'},
remote_addr => $param->{'remote_addr'},
current_email => $current_email,
email => $email,
}
);
unless (defined $status) {
unless ($spindle and $spindle->spin) {
wwslog('err', 'Failed to change user email address');
return undef;
}
 
foreach my $failed_list (@$failed_for) {
Sympa::Report::reject_report_web(
'user',
'change_member_email_failed_included',
{'listname' => $failed_list->{'name'}},
$param->{'action'},
$failed_list,
$old_email,
$robot
);
web_db_log(
{ 'robot' => $in{'robot'},
'list' => $failed_list->{'name'},
'action' => $param->{'action'},
'parameters' => $new_email,
'target_email' => $new_email,
'msg_id' => '',
'status' => 'error',
'error_type' => 'internal',
'user_email' => $old_email,
}
);
}
Sympa::Report::notice_report_web('performed', {}, $param->{'action'});
## Change email as list OWNER/MODERATOR
my %updated_lists;
foreach my $role ('owner', 'editor') {
foreach my $list (Sympa::List::get_which($old_email, $robot, $role)) {
## Check if admin is include via an external datasource
my ($admin_user) =
@{$list->get_admins($role, filter => [email => $old_email])};
if ($admin_user and $admin_user->{'included'}) {
## Notify listmaster
Sympa::send_notify_to_listmaster(
$list,
'failed_to_change_included_admin',
{ 'current_email' => $old_email,
'new_email' => $new_email,
'datasource' =>
$list->get_datasource_name($admin_user->{'id'})
}
);
Sympa::Report::reject_report_web(
'user',
'change_admin_email_failed_included',
{'listname' => $list->{'name'}},
$param->{'action'},
$list,
$old_email,
$robot
);
wwslog(
'err',
'Could not change %s email for list %s because admin is included',
$role,
$list->{'name'}
);
next;
}
## Go through owners/editors of the list
foreach my $admin (@{$list->{'admin'}{$role}}) {
next unless (lc($admin->{'email'}) eq lc($old_email));
## Update entry with new email address
$admin->{'email'} = $new_email;
$updated_lists{$list->{'name'}}++;
}
## Update Db cache for the list
$list->sync_include_admin();
$list->save_config($param->{'session'}{'email'});
foreach my $report (@{$spindle->{stash} || []}) {
if ($report->[1] eq 'notice') {
Sympa::Report::notice_report_web(@{$report}[2, 3],
$param->{'action'});
} else {
Sympa::Report::reject_report_web(@{$report}[1 .. 3],
$param->{action});
}
}
## Notify listmasters that list owners/moderators email have changed
if (keys %updated_lists) {
Sympa::send_notify_to_listmaster(
$robot,
'listowner_email_changed',
{ 'list' => $list,
'previous_email' => $old_email,
'new_email' => $new_email,
'updated_lists' => keys %updated_lists
}
);
}
## Update User_table and remove existing entry first (to avoid duplicate
## entries)
Sympa::User::delete_global_user($new_email,);
unless (
Sympa::User::update_global_user(
$old_email,
{ 'email' => $new_email,
}
)
) {
Sympa::Report::reject_report_web(
'intern',
'update_user_db_failed',
{ 'user' => $param->{'user'},
'old_email' => $old_email
},
$param->{'action'},
'',
$old_email,
$robot
);
wwslog('info', 'Update failed');
web_db_log(
{ 'robot' => $robot,
'list' => $list->{'name'},
'action' => $param->{'action'},
'parameters' => "$new_email",
'target_email' => "$new_email",
'msg_id' => '',
'status' => 'error',
'error_type' => 'internal',
'user_email' => $old_email,
}
);
return undef;
}
## Update netidmap_table
unless (
Sympa::Robot::update_email_netidmap_db(
$robot, $old_email, $new_email
)
) {
Sympa::Report::reject_report_web(
'intern',
'update_netidmap_failed',
{ 'user' => $param->{'user'},
'old_email' => $old_email
},
$param->{'action'},
'',
$param->{'user'}{'email'},
$robot
);
wwslog('info', 'Update failed');
web_db_log(
{ 'target_email' => $old_email,
'status' => 'error',
'error_type' => 'internal'
}
);
return undef;
}
if ($edited_by_listmaster == 1) {
return 'serveradmin';
}
if ($in{'previous_action'}) {
$in{'list'} = $in{'previous_list'};
return $in{'previous_action'};
} elsif ($edited_by_listmaster == 1) {
return 'serveradmin';
unless (@{$spindle->{stash} || []}) {
Sympa::Report::notice_report_web('performed', {}, $param->{'action'});
}
 
return 'pref';
return $in{'previous_action'} || 'pref';
}
 
sub do_suspend {
......
......@@ -98,6 +98,7 @@ nobase_modules_DATA = \
Sympa/Request/Handler/last.pm \
Sympa/Request/Handler/lists.pm \
Sympa/Request/Handler/modindex.pm \
Sympa/Request/Handler/move_user.pm \
Sympa/Request/Handler/reject.pm \
Sympa/Request/Handler/remind.pm \
Sympa/Request/Handler/review.pm \
......
......@@ -528,12 +528,6 @@ sub send_notify_to_user {
'family_signoff/' . $param->{family} . '/' . $user,
$param->{ip})
or return undef;
} elsif ($operation eq 'ticket_to_send') {
$param->{'one_time_ticket'} =
Sympa::Ticket::create($user, $robot_id,
'change_email/' . $param->{email},
$param->{ip})
or return undef;
}
unless (Sympa::send_file($that, 'user_notification', $user, $param)) {
......
......@@ -58,7 +58,6 @@ use Sympa::Robot;
use Sympa::Scenario;
use Sympa::Template;
use Sympa::Tools::File;
use Sympa::User;
my $language = Sympa::Language->instance;
my $log = Sympa::Log->instance;
......@@ -1560,177 +1559,8 @@ sub check_topics {
return undef;
}
# change a user email address for both his memberships and ownerships
#
# IN : - current_email : current user email address
# - new_email : new user email address
# - robot : virtual robot
#
# OUT : - status(scalar) : status of the subroutine
# - failed_for(arrayref) : list of lists for which the change could
# not be done (because user was
# included or for authorization reasons)
sub change_user_email {
my %in = @_;
my $robot_id = $in{'robot'};
my @failed_for;
unless ($in{'current_email'} and $in{'new_email'} and $in{'robot'}) {
die 'Missing incoming parameter';
}
## Change email as list MEMBER
foreach my $list (
Sympa::List::get_which($in{'current_email'}, $robot_id, 'member')) {
my $l = $list->{'name'};
my $user_entry = $list->get_list_member($in{'current_email'});
if ($user_entry->{'included'} == 1) {
# Check the type of data sources.
# If only include_sympa_list of local mailing lists, then no
# problem. Otherwise, notify list owner.
# We could also force a sync_include for local lists.
my $use_external_data_sources;
foreach my $datasource_id (split(/,/, $user_entry->{'id'})) {