aboutsummaryrefslogtreecommitdiffstats
path: root/perllib/FixMyStreet/App
diff options
context:
space:
mode:
Diffstat (limited to 'perllib/FixMyStreet/App')
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin.pm105
-rw-r--r--perllib/FixMyStreet/App/Controller/Alert.pm9
-rw-r--r--perllib/FixMyStreet/App/Controller/Around.pm11
-rw-r--r--perllib/FixMyStreet/App/Controller/Auth.pm72
-rw-r--r--perllib/FixMyStreet/App/Controller/Auth/Phone.pm8
-rw-r--r--perllib/FixMyStreet/App/Controller/Auth/Profile.pm61
-rw-r--r--perllib/FixMyStreet/App/Controller/Dashboard.pm9
-rw-r--r--perllib/FixMyStreet/App/Controller/Moderate.pm3
-rw-r--r--perllib/FixMyStreet/App/Controller/Open311.pm2
-rw-r--r--perllib/FixMyStreet/App/Controller/Photo.pm2
-rw-r--r--perllib/FixMyStreet/App/Controller/Report/New.pm70
-rw-r--r--perllib/FixMyStreet/App/Controller/Report/Update.pm35
-rw-r--r--perllib/FixMyStreet/App/Controller/Reports.pm22
-rwxr-xr-xperllib/FixMyStreet/App/Controller/Rss.pm7
-rw-r--r--perllib/FixMyStreet/App/Response.pm2
15 files changed, 307 insertions, 111 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm
index a5c29fce3..85b6204fc 100644
--- a/perllib/FixMyStreet/App/Controller/Admin.pm
+++ b/perllib/FixMyStreet/App/Controller/Admin.pm
@@ -892,7 +892,7 @@ sub report_edit : Path('report_edit') : Args(1) {
$self->remove_photo($c, $problem, $remove_photo_param);
}
- if ( $remove_photo_param || $problem->state eq 'hidden' ) {
+ if ($problem->state eq 'hidden') {
$problem->get_photoset->delete_cached;
}
@@ -1274,8 +1274,14 @@ sub update_edit : Path('update_edit') : Args(1) {
$self->remove_photo($c, $update, $remove_photo_param);
}
- if ( $remove_photo_param || $new_state eq 'hidden' ) {
- $update->get_photoset->delete_cached;
+ $c->stash->{status_message} = '<p><em>' . _('Updated!') . '</em></p>';
+
+ # Must call update->hide while it's not hidden (so is_latest works)
+ if ($new_state eq 'hidden') {
+ my $outcome = $update->hide;
+ $c->stash->{status_message} .=
+ '<p><em>' . _('Problem marked as open.') . '</em></p>'
+ if $outcome->{reopened};
}
$update->name( $c->get_param('name') || '' );
@@ -1296,19 +1302,6 @@ sub update_edit : Path('update_edit') : Args(1) {
$update->update;
- $c->stash->{status_message} = '<p><em>' . _('Updated!') . '</em></p>';
-
- # If we're hiding an update, see if it marked as fixed and unfix if so
- if ( $new_state eq 'hidden' && $update->mark_fixed ) {
- if ( $update->problem->state =~ /^fixed/ ) {
- $update->problem->state('confirmed');
- $update->problem->update;
- }
-
- $c->stash->{status_message} .=
- '<p><em>' . _('Problem marked as open.') . '</em></p>';
- }
-
if ( $new_state ne $old_state ) {
$c->forward( 'log_edit',
[ $update->id, 'update', 'state_change' ] );
@@ -1426,11 +1419,19 @@ sub user_edit : Path('user_edit') : Args(1) {
'<p><em>' . $c->flash->{status_message} . '</em></p>';
}
+ $c->forward('/auth/check_csrf_token') if $c->get_param('submit');
+
if ( $c->get_param('submit') and $c->get_param('unban') ) {
- $c->forward('/auth/check_csrf_token');
$c->forward('unban_user', [ $user ]);
+ } elsif ( $c->get_param('submit') and $c->get_param('logout_everywhere') ) {
+ $c->forward('user_logout_everywhere', [ $user ]);
+ } elsif ( $c->get_param('submit') and $c->get_param('anon_everywhere') ) {
+ $c->forward('user_anon_everywhere', [ $user ]);
+ } elsif ( $c->get_param('submit') and $c->get_param('hide_everywhere') ) {
+ $c->forward('user_hide_everywhere', [ $user ]);
+ } elsif ( $c->get_param('submit') and $c->get_param('remove_account') ) {
+ $c->forward('user_remove_account', [ $user ]);
} elsif ( $c->get_param('submit') ) {
- $c->forward('/auth/check_csrf_token');
my $edited = 0;
@@ -1759,6 +1760,58 @@ sub ban_user : Private {
return 1;
}
+sub user_logout_everywhere : Private {
+ my ( $self, $c, $user ) = @_;
+ my $sessions = $user->get_extra_metadata('sessions');
+ foreach (grep { $_ ne $c->sessionid } @$sessions) {
+ $c->delete_session_data("session:$_");
+ }
+ $c->stash->{status_message} = _('That user has been logged out.');
+}
+
+sub user_anon_everywhere : Private {
+ my ( $self, $c, $user ) = @_;
+ $user->problems->update({anonymous => 1});
+ $user->comments->update({anonymous => 1});
+ $c->stash->{status_message} = _('That user has been made anonymous on all reports and updates.');
+}
+
+sub user_hide_everywhere : Private {
+ my ( $self, $c, $user ) = @_;
+ my $problems = $user->problems->search({ state => { '!=' => 'hidden' } });
+ while (my $problem = $problems->next) {
+ $problem->get_photoset->delete_cached;
+ $problem->update({ state => 'hidden' });
+ }
+ my $updates = $user->comments->search({ state => { '!=' => 'hidden' } });
+ while (my $update = $updates->next) {
+ $update->hide;
+ }
+ $c->stash->{status_message} = _('That user’s reports and updates have been hidden.');
+}
+
+# Anonymize and remove name from all problems/updates, disable all alerts.
+# Remove their account's email address, phone number, password, etc.
+sub user_remove_account : Private {
+ my ( $self, $c, $user ) = @_;
+ $c->forward('user_logout_everywhere', [ $user ]);
+ $user->problems->update({ anonymous => 1, name => '', send_questionnaire => 0 });
+ $user->comments->update({ anonymous => 1, name => '' });
+ $user->alerts->update({ whendisabled => \'current_timestamp' });
+ $user->password('', 1);
+ $user->update({
+ email => 'removed-' . $user->id . '@' . FixMyStreet->config('EMAIL_DOMAIN'),
+ email_verified => 0,
+ name => '',
+ phone => '',
+ phone_verified => 0,
+ title => undef,
+ twitter_id => undef,
+ facebook_id => undef,
+ });
+ $c->stash->{status_message} = _('That user’s personal details have been removed.');
+}
+
sub unban_user : Private {
my ( $self, $c, $user ) = @_;
@@ -1904,6 +1957,7 @@ sub remove_photo : Private {
my ($self, $c, $object, $keys) = @_;
if ($keys eq 'ALL') {
$object->photo(undef);
+ $object->get_photoset->delete_cached;
} else {
my $fileids = $object->get_photoset->remove_images($keys);
$object->photo($fileids);
@@ -1938,11 +1992,9 @@ sub check_page_allowed : Private {
sub fetch_all_bodies : Private {
my ($self, $c ) = @_;
- my @bodies = $c->model('DB::Body')->all_translated;
+ my @bodies = $c->model('DB::Body')->translated->all_sorted;
if ( $c->cobrand->moniker eq 'zurich' ) {
@bodies = $c->cobrand->admin_fetch_all_bodies( @bodies );
- } else {
- @bodies = sort { strcoll($a->name, $b->name) } @bodies;
}
$c->stash->{bodies} = \@bodies;
@@ -1952,20 +2004,15 @@ sub fetch_all_bodies : Private {
sub fetch_body_areas : Private {
my ($self, $c, $body ) = @_;
- my $body_area = $body->body_areas->first;
-
- unless ( $body_area ) {
+ my $children = $body->first_area_children;
+ unless ($children) {
# Body doesn't have any areas defined.
delete $c->stash->{areas};
delete $c->stash->{fetched_areas_body_id};
return;
}
- my $areas = mySociety::MaPit::call('area/children', [ $body_area->area_id ],
- type => $c->cobrand->area_types_children,
- );
-
- $c->stash->{areas} = [ sort { strcoll($a->{name}, $b->{name}) } values %$areas ];
+ $c->stash->{areas} = [ sort { strcoll($a->{name}, $b->{name}) } values %$children ];
# Keep track of the areas we've fetched to prevent a duplicate fetch later on
$c->stash->{fetched_areas_body_id} = $body->id;
}
diff --git a/perllib/FixMyStreet/App/Controller/Alert.pm b/perllib/FixMyStreet/App/Controller/Alert.pm
index 5c9fbad1b..9d522dbc9 100644
--- a/perllib/FixMyStreet/App/Controller/Alert.pm
+++ b/perllib/FixMyStreet/App/Controller/Alert.pm
@@ -281,20 +281,25 @@ then display confirmation page.
sub send_confirmation_email : Private {
my ( $self, $c ) = @_;
+ my $user = $c->stash->{alert}->user;
+
+ # Superusers using 2FA can not log in by code
+ $c->detach( '/page_error_403_access_denied', [] ) if $user->has_2fa;
+
my $token = $c->model("DB::Token")->create(
{
scope => 'alert',
data => {
id => $c->stash->{alert}->id,
type => 'subscribe',
- email => $c->stash->{alert}->user->email
+ email => $user->email
}
}
);
$c->stash->{token_url} = $c->uri_for_email( '/A', $token->token );
- $c->send_email( 'alert-confirm.txt', { to => $c->stash->{alert}->user->email } );
+ $c->send_email( 'alert-confirm.txt', { to => $user->email } );
$c->stash->{email_type} = 'alert';
$c->stash->{template} = 'email_sent.html';
diff --git a/perllib/FixMyStreet/App/Controller/Around.pm b/perllib/FixMyStreet/App/Controller/Around.pm
index da17cbd56..ae7d83f55 100644
--- a/perllib/FixMyStreet/App/Controller/Around.pm
+++ b/perllib/FixMyStreet/App/Controller/Around.pm
@@ -225,10 +225,7 @@ sub check_and_stash_category : Private {
my ( $self, $c ) = @_;
my $all_areas = $c->stash->{all_areas};
- my @bodies = $c->model('DB::Body')->search(
- { 'body_areas.area_id' => [ keys %$all_areas ], deleted => 0 },
- { join => 'body_areas' }
- )->all;
+ my @bodies = $c->model('DB::Body')->active->for_areas(keys %$all_areas)->all;
my %bodies = map { $_->id => $_ } @bodies;
my @contacts = $c->model('DB::Contact')->not_deleted->search(
@@ -243,7 +240,7 @@ sub check_and_stash_category : Private {
)->all;
my @categories = map { { name => $_->category, value => $_->category_display } } @contacts;
$c->stash->{filter_categories} = \@categories;
- my %categories_mapped = map { $_ => 1 } @categories;
+ my %categories_mapped = map { $_->{name} => 1 } @categories;
my $categories = [ $c->get_param_list('filter_category', 1) ];
my %valid_categories = map { $_ => 1 } grep { $_ && $categories_mapped{$_} } @$categories;
@@ -287,11 +284,11 @@ sub map_features : Private {
Handle the ajax calls that the map makes when it is dragged. The info returned
is used to update the pins on the map and the text descriptions on the side of
-the map.
+the map. Used via /around?ajax=1 but also available at /ajax for mobile app.
=cut
-sub ajax : Private {
+sub ajax : Path('/ajax') {
my ( $self, $c ) = @_;
my $ret = $c->forward('/location/determine_location_from_bbox');
diff --git a/perllib/FixMyStreet/App/Controller/Auth.pm b/perllib/FixMyStreet/App/Controller/Auth.pm
index 455022e03..533e6a9be 100644
--- a/perllib/FixMyStreet/App/Controller/Auth.pm
+++ b/perllib/FixMyStreet/App/Controller/Auth.pm
@@ -5,6 +5,7 @@ use namespace::autoclean;
BEGIN { extends 'Catalyst::Controller'; }
use Email::Valid;
+use Data::Password::Common 'found';
use Digest::HMAC_SHA1 qw(hmac_sha1);
use JSON::MaybeXS;
use MIME::Base64;
@@ -84,6 +85,12 @@ sub sign_in : Private {
my $parsed = FixMyStreet::SMS->parse_username($username);
if ($parsed->{username} && $password && $c->forward('authenticate', [ $parsed->{type}, $parsed->{username}, $password ])) {
+ # Upgrade hash count if necessary
+ my $cost = sprintf("%02d", FixMyStreet::DB::Result::User->cost);
+ if ($c->user->password !~ /^\$2a\$$cost\$/) {
+ $c->user->update({ password => $password });
+ }
+
# unless user asked to be remembered limit the session to browser
$c->set_session_cookie_expire(0)
unless $remember_me;
@@ -143,6 +150,11 @@ sub email_sign_in : Private {
return;
}
+ my $password = $c->get_param('password_register');
+ if ($password) {
+ return unless $c->forward('/auth/test_password', [ $password ]);
+ }
+
# If user registration is disabled then bail out at this point
# if there's not already a user with this email address.
# NB this uses the same template as a successful sign in to stop
@@ -156,8 +168,7 @@ sub email_sign_in : Private {
}
my $user_params = {};
- $user_params->{password} = $c->get_param('password_register')
- if $c->get_param('password_register');
+ $user_params->{password} = $password if $password;
my $user = $c->model('DB::User')->new( $user_params );
my $token_data = {
@@ -232,6 +243,9 @@ sub process_login : Private {
$c->detach( '/page_error_403_access_denied', [] )
if FixMyStreet->config('SIGNUPS_DISABLED') && !$user->in_storage && !$data->{old_user_id};
+ # Superusers using 2FA can not log in by code
+ $c->detach( '/page_error_403_access_denied', [] ) if $user->has_2fa;
+
if ($data->{old_user_id}) {
# Were logged in as old_user_id, want to switch to $user
if ($user->in_storage) {
@@ -270,6 +284,11 @@ Used after signing in to take the person back to where they were.
sub redirect_on_signin : Private {
my ( $self, $c, $redirect, $params ) = @_;
+
+ if ($c->stash->{detach_to}) {
+ $c->detach($c->stash->{detach_to}, $c->stash->{detach_args});
+ }
+
unless ( $redirect ) {
$c->detach('redirect_to_categories') if $c->user->from_body && scalar @{ $c->user->categories };
$redirect = 'my';
@@ -348,6 +367,55 @@ sub no_csrf_token : Private {
$c->detach('/page_error_400_bad_request', []);
}
+=item common_password
+
+Returns 1/0 depending on if password is common or not.
+
+=cut
+
+sub common_password : Local : Args(0) {
+ my ($self, $c) = @_;
+
+ my $password = $c->get_param('password_register');
+
+ my $return = JSON->true;
+ if (!$c->cobrand->call_hook('bypass_password_checks') && found($password)) {
+ $return = _('Please choose a less commonly-used password');
+ }
+
+ my $body = JSON->new->utf8->allow_nonref->encode($return);
+ $c->res->content_type('application/json; charset=utf-8');
+ $c->res->body($body);
+}
+
+=item test_password
+
+Checks a password is not too weak; returns true if okay,
+false if weak (and sets stash error).
+
+=cut
+
+sub test_password : Private {
+ my ($self, $c, $password) = @_;
+
+ return 1 if $c->cobrand->call_hook('bypass_password_checks');
+
+ my @errors;
+
+ my $min_length = $c->cobrand->password_minimum_length;
+ push @errors, sprintf(_('Please make sure your password is at least %d characters long'), $min_length)
+ if length($password) < $min_length;
+
+ push @errors, _('Please choose a less commonly-used password')
+ if found($password);
+
+ if (@errors) {
+ $c->stash->{field_errors}->{password_register} = join('<br>', @errors);
+ return 0;
+ }
+ return 1;
+}
+
=head2 sign_out
Log the user out. Tell them we've done so.
diff --git a/perllib/FixMyStreet/App/Controller/Auth/Phone.pm b/perllib/FixMyStreet/App/Controller/Auth/Phone.pm
index 8387b9d64..8e3150df9 100644
--- a/perllib/FixMyStreet/App/Controller/Auth/Phone.pm
+++ b/perllib/FixMyStreet/App/Controller/Auth/Phone.pm
@@ -59,6 +59,11 @@ sub sign_in : Private {
return;
}
+ my $password = $c->get_param('password_register');
+ if ($password) {
+ return unless $c->forward('/auth/test_password', [ $password ]);
+ }
+
(my $number = $parsed->{phone}->format) =~ s/\s+//g;
if ( FixMyStreet->config('SIGNUPS_DISABLED')
@@ -70,8 +75,7 @@ sub sign_in : Private {
}
my $user_params = {};
- $user_params->{password} = $c->get_param('password_register')
- if $c->get_param('password_register');
+ $user_params->{password} = $password if $password;
my $user = $c->model('DB::User')->new( $user_params );
my $token_data = {
diff --git a/perllib/FixMyStreet/App/Controller/Auth/Profile.pm b/perllib/FixMyStreet/App/Controller/Auth/Profile.pm
index 5e6fe6266..87aff2261 100644
--- a/perllib/FixMyStreet/App/Controller/Auth/Profile.pm
+++ b/perllib/FixMyStreet/App/Controller/Auth/Profile.pm
@@ -19,7 +19,7 @@ verifying email, phone, password.
=cut
-sub auto {
+sub auto : Private {
my ( $self, $c ) = @_;
$c->detach( '/auth/redirect' ) unless $c->user;
@@ -49,10 +49,20 @@ sub change_password : Path('/auth/change_password') {
my $new = $c->get_param('new_password') // '';
my $confirm = $c->get_param('confirm') // '';
+ my $password_error;
+
+ # Check existing password, if available
+ if ($c->user->password) {
+ my $current = $c->get_param('current_password') // '';
+ $c->stash->{current_password} = $current;
+ $password_error = 'incorrect' unless $c->user->check_password($current);
+ }
+
# check for errors
- my $password_error =
+ $password_error ||=
!$new && !$confirm ? 'missing'
: $new ne $confirm ? 'mismatch'
+ : !$c->forward('/auth/test_password', [ $new ]) ? 'failed'
: '';
if ($password_error) {
@@ -62,10 +72,17 @@ sub change_password : Path('/auth/change_password') {
return;
}
- # we should have a usable password - save it to the user
- $c->user->obj->update( { password => $new } );
- $c->stash->{password_changed} = 1;
-
+ if ($c->user->password) {
+ # we should have a usable password - save it to the user
+ $c->user->obj->update( { password => $new } );
+ $c->stash->{password_changed} = 1;
+ } else {
+ # Set up arguments for code sign in
+ $c->set_param('username', $c->user->username);
+ $c->set_param('password_register', $new);
+ $c->set_param('r', 'auth/change_password/success');
+ $c->detach('/auth/code_sign_in');
+ }
}
=head2 change_email
@@ -148,6 +165,12 @@ sub change_phone_success : Path('/auth/change_phone/success') {
$c->res->redirect('/my');
}
+sub change_password_success : Path('/auth/change_password/success') {
+ my ( $self, $c ) = @_;
+ $c->flash->{flash_message} = _('Your password has been changed');
+ $c->res->redirect('/my');
+}
+
sub generate_token : Path('/auth/generate_token') {
my ($self, $c) = @_;
@@ -157,14 +180,34 @@ sub generate_token : Path('/auth/generate_token') {
$c->stash->{template} = 'auth/generate_token.html';
$c->forward('/auth/get_csrf_token');
+ my $has_2fa = $c->user->get_extra_metadata('2fa_secret');
+
if ($c->req->method eq 'POST') {
$c->forward('/auth/check_csrf_token');
- my $token = mySociety::AuthToken::random_token();
- $c->user->set_extra_metadata('access_token', $token);
+
+ if ($c->get_param('generate_token')) {
+ my $token = mySociety::AuthToken::random_token();
+ $c->user->set_extra_metadata('access_token', $token);
+ $c->stash->{token_generated} = 1;
+ }
+
+ if ($c->get_param('toggle_2fa') && $c->user->is_superuser) {
+ if ($has_2fa) {
+ $c->user->unset_extra_metadata('2fa_secret');
+ $c->stash->{toggle_2fa_off} = 1;
+ } else {
+ my $auth = Auth::GoogleAuth->new;
+ $c->stash->{qr_code} = $auth->qr_code(undef, $c->user->email, 'FixMyStreet');
+ $c->stash->{secret32} = $auth->secret32;
+ $c->user->set_extra_metadata('2fa_secret', $auth->secret32);
+ $c->stash->{toggle_2fa_on} = 1;
+ }
+ }
+
$c->user->update();
- $c->stash->{token_generated} = 1;
}
+ $c->stash->{has_2fa} = $has_2fa ? 1 : 0;
$c->stash->{existing_token} = $c->user->get_extra_metadata('access_token');
}
diff --git a/perllib/FixMyStreet/App/Controller/Dashboard.pm b/perllib/FixMyStreet/App/Controller/Dashboard.pm
index 926e941f6..032c36e05 100644
--- a/perllib/FixMyStreet/App/Controller/Dashboard.pm
+++ b/perllib/FixMyStreet/App/Controller/Dashboard.pm
@@ -86,11 +86,7 @@ sub index : Path : Args(0) {
if ($body) {
$c->stash->{body_name} = $body->name;
- my $area_id = $body->body_areas->first->area_id;
- my $children = mySociety::MaPit::call('area/children', $area_id,
- type => $c->cobrand->area_types_children,
- );
- $c->stash->{children} = $children;
+ my $children = $c->stash->{children} = $body->first_area_children;
$c->forward('/admin/fetch_contacts');
$c->stash->{contacts} = [ $c->stash->{contacts}->all ];
@@ -103,7 +99,8 @@ sub index : Path : Args(0) {
$c->stash->{body_name} = join "", map { $children->{$_}->{name} } grep { $children->{$_} } $c->user->area_id;
}
} else {
- $c->forward('/admin/fetch_all_bodies');
+ my @bodies = $c->model('DB::Body')->active->translated->with_area_count->all_sorted;
+ $c->stash->{bodies} = \@bodies;
}
$c->stash->{start_date} = $c->get_param('start_date');
diff --git a/perllib/FixMyStreet/App/Controller/Moderate.pm b/perllib/FixMyStreet/App/Controller/Moderate.pm
index a8e0b7a3c..86143b5ea 100644
--- a/perllib/FixMyStreet/App/Controller/Moderate.pm
+++ b/perllib/FixMyStreet/App/Controller/Moderate.pm
@@ -128,6 +128,7 @@ sub report_moderate_hide : Private {
if ($c->get_param('problem_hide')) {
$problem->update({ state => 'hidden' });
+ $problem->get_photoset->delete_cached;
$c->res->redirect( '/' ); # Go directly to front-page
$c->detach( 'report_moderate_audit', ['hide'] ); # break chain here.
@@ -267,7 +268,7 @@ sub update_moderate_hide : Private {
my $comment = $c->stash->{comment} or die;
if ($c->get_param('update_hide')) {
- $comment->update({ state => 'hidden' });
+ $comment->hide;
$c->detach( 'update_moderate_audit', ['hide'] ); # break chain here.
}
return;
diff --git a/perllib/FixMyStreet/App/Controller/Open311.pm b/perllib/FixMyStreet/App/Controller/Open311.pm
index 95b29d116..c7e4e5bee 100644
--- a/perllib/FixMyStreet/App/Controller/Open311.pm
+++ b/perllib/FixMyStreet/App/Controller/Open311.pm
@@ -284,7 +284,7 @@ sub output_requests : Private {
my $display_photos = $c->cobrand->allow_photo_display($problem);
if ($display_photos && $problem->photo) {
my $url = $c->cobrand->base_url();
- my $imgurl = $url . $problem->photos->[0]->{url_full};
+ my $imgurl = $url . $problem->photos->[$display_photos-1]->{url_full};
$request->{'media_url'} = $imgurl;
}
push(@problemlist, $request);
diff --git a/perllib/FixMyStreet/App/Controller/Photo.pm b/perllib/FixMyStreet/App/Controller/Photo.pm
index 2302322bf..f41702dcf 100644
--- a/perllib/FixMyStreet/App/Controller/Photo.pm
+++ b/perllib/FixMyStreet/App/Controller/Photo.pm
@@ -63,7 +63,7 @@ sub index :LocalRegex('^(c/)?([1-9]\d*)(?:\.(\d+))?(?:\.(full|tn|fp))?\.(?:jpeg|
$c->detach( 'no_photo' ) unless $item;
- $c->detach( 'no_photo' ) unless $c->cobrand->allow_photo_display($item); # Should only be for reports, not updates
+ $c->detach( 'no_photo' ) unless $c->cobrand->allow_photo_display($item, $photo_number); # Should only be for reports, not updates
my $photo;
$photo = $item->get_photoset
diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm
index ca4fa2fd2..f9e07dd41 100644
--- a/perllib/FixMyStreet/App/Controller/Report/New.pm
+++ b/perllib/FixMyStreet/App/Controller/Report/New.pm
@@ -109,8 +109,8 @@ sub report_new : Path : Args(0) {
return unless $c->forward('check_form_submitted');
$c->forward('/auth/check_csrf_token');
- $c->forward('process_user');
$c->forward('process_report');
+ $c->forward('process_user');
$c->forward('/photo/process_photo');
return unless $c->forward('check_for_errors');
$c->forward('save_user_and_report');
@@ -147,8 +147,8 @@ sub report_new_ajax : Path('mobile') : Args(0) {
$c->forward('setup_categories_and_bodies');
$c->forward('setup_report_extra_fields');
- $c->forward('process_user');
$c->forward('process_report');
+ $c->forward('process_user');
$c->forward('/photo/process_photo');
unless ($c->forward('check_for_errors')) {
@@ -418,7 +418,9 @@ sub report_import : Path('/import') {
sub oauth_callback : Private {
my ( $self, $c, $token_code ) = @_;
- $c->stash->{oauth_report} = $token_code;
+ my $auth_token = $c->forward(
+ '/tokens/load_auth_token', [ $token_code, 'problem/social' ]);
+ $c->stash->{oauth_report} = $auth_token->data;
$c->detach('report_new');
}
@@ -475,9 +477,7 @@ sub initialize_report : Private {
}
if (!$report && $c->stash->{oauth_report}) {
- my $auth_token = $c->forward( '/tokens/load_auth_token',
- [ $c->stash->{oauth_report}, 'problem/social' ] );
- $report = $c->model("DB::Problem")->new($auth_token->data);
+ $report = $c->model("DB::Problem")->new($c->stash->{oauth_report});
}
if ($report) {
@@ -617,10 +617,7 @@ sub setup_categories_and_bodies : Private {
my $all_areas = $c->stash->{all_areas};
my $first_area = ( values %$all_areas )[0];
- my @bodies = $c->model('DB::Body')->search(
- { 'body_areas.area_id' => [ keys %$all_areas ], deleted => 0 },
- { join => 'body_areas' }
- )->all;
+ my @bodies = $c->model('DB::Body')->active->for_areas(keys %$all_areas)->all;
my %bodies = map { $_->id => $_ } @bodies;
my $first_body = ( values %bodies )[0];
@@ -775,7 +772,7 @@ sub process_user : Private {
if ( $c->user_exists ) { {
my $user = $c->user->obj;
- if ($c->stash->{contributing_as_another_user} = $user->contributing_as('another_user', $c, $c->stash->{bodies})) {
+ if ($c->stash->{contributing_as_another_user}) {
# Act as if not logged in (and it will be auto-confirmed later on)
$report->user(undef);
last;
@@ -784,8 +781,7 @@ sub process_user : Private {
$report->user( $user );
$c->forward('update_user', [ \%params ]);
- if ($c->stash->{contributing_as_body} = $user->contributing_as('body', $c, $c->stash->{bodies}) or
- $c->stash->{contributing_as_anonymous_user} = $user->contributing_as('anonymous_user', $c, $c->stash->{bodies})) {
+ if ($c->stash->{contributing_as_body} or $c->stash->{contributing_as_anonymous_user}) {
$report->name($user->from_body->name);
$user->name($user->from_body->name) unless $user->name;
$c->stash->{no_reporter_alert} = 1;
@@ -794,14 +790,27 @@ sub process_user : Private {
return 1;
} }
+ if ( $c->stash->{contributing_as_another_user} && !$params{username} ) {
+ # If the 'username' (i.e. email) field is blank, then use the phone
+ # field for the username.
+ $params{username} = $params{phone};
+ }
+
my $parsed = FixMyStreet::SMS->parse_username($params{username});
my $type = $parsed->{type} || 'email';
- $type = 'email' unless FixMyStreet->config('SMS_AUTHENTICATION');
+ $type = 'email' unless FixMyStreet->config('SMS_AUTHENTICATION') || $c->stash->{contributing_as_another_user};
$report->user( $c->model('DB::User')->find_or_new( { $type => $parsed->{username} } ) )
unless $report->user;
+ $c->stash->{phone_may_be_mobile} = $type eq 'phone' && $parsed->{may_be_mobile};
+
# The user is trying to sign in. We only care about username from the params.
if ( $c->get_param('submit_sign_in') || $c->get_param('password_sign_in') ) {
+ $c->stash->{tfa_data} = {
+ detach_to => '/report/new/report_new',
+ login_success => 1,
+ oauth_report => { $report->get_inflated_columns }
+ };
unless ( $c->forward( '/auth/sign_in', [ $params{username} ] ) ) {
$c->stash->{field_errors}->{password} = _('There was a problem with your login information. If you cannot remember your password, or do not have one, please fill in the &lsquo;No&rsquo; section of the form.');
return 1;
@@ -816,8 +825,10 @@ sub process_user : Private {
}
$c->forward('update_user', [ \%params ]);
- $report->user->password( Utils::trim_text( $params{password_register} ) )
- if $params{password_register};
+ if ($params{password_register}) {
+ $c->forward('/auth/test_password', [ $params{password_register} ]);
+ $report->user->password(Utils::trim_text($params{password_register}));
+ }
return 1;
}
@@ -870,6 +881,13 @@ sub process_report : Private {
$report->longitude( $c->stash->{longitude} );
$report->send_questionnaire( $c->cobrand->send_questionnaires() );
+ if ( $c->user_exists ) {
+ my $user = $c->user->obj;
+ $c->stash->{contributing_as_another_user} = $user->contributing_as('another_user', $c, $c->stash->{bodies});
+ $c->stash->{contributing_as_body} = $user->contributing_as('body', $c, $c->stash->{bodies});
+ $c->stash->{contributing_as_anonymous_user} = $user->contributing_as('anonymous_user', $c, $c->stash->{bodies});
+ }
+
# set some simple bool values (note they get inverted)
if ($c->stash->{contributing_as_body}) {
$report->anonymous(0);
@@ -960,7 +978,7 @@ sub contacts_to_bodies : Private {
my @contacts = grep { $_->category eq $category } @{$c->stash->{contacts}};
- if ($c->stash->{unresponsive}{$category} || $c->stash->{unresponsive}{ALL}) {
+ if ($c->stash->{unresponsive}{$category} || $c->stash->{unresponsive}{ALL} || !@contacts) {
[];
} else {
if ( $c->cobrand->call_hook('singleton_bodies_str') ) {
@@ -1066,6 +1084,12 @@ sub check_for_errors : Private {
delete $field_errors{username};
}
+ # if we're contributing as someone else then allow landline numbers
+ if ( $field_errors{phone} && $c->stash->{contributing_as_another_user} && !$c->stash->{phone_may_be_mobile}) {
+ delete $field_errors{username};
+ delete $field_errors{phone};
+ }
+
# add the photo error if there is one.
if ( my $photo_error = delete $c->stash->{photo_error} ) {
$field_errors{photo} = $photo_error;
@@ -1374,10 +1398,11 @@ sub redirect_or_confirm_creation : Private {
if ( $report->confirmed ) {
# Subscribe problem reporter to email updates
$c->forward( 'create_reporter_alert' );
- if ($c->stash->{contributing_as_another_user}) {
- $c->send_email( 'other-reported.txt', {
- to => [ [ $report->user->email, $report->name ] ],
- } );
+ if ($c->stash->{contributing_as_another_user} && $report->user->email
+ && !$c->cobrand->report_sent_confirmation_email) {
+ $c->send_email( 'other-reported.txt', {
+ to => [ [ $report->user->email, $report->name ] ],
+ } );
}
# If the user has shortlist permission, and either we're not on a
# council cobrand or the just-created problem is owned by the cobrand
@@ -1394,6 +1419,9 @@ sub redirect_or_confirm_creation : Private {
return 1;
}
+ # Superusers using 2FA can not log in by code
+ $c->detach( '/page_error_403_access_denied', [] ) if $report->user->has_2fa;
+
# otherwise email or text a confirm token to them.
my $thing = 'email';
if ($report->user->email_verified) {
diff --git a/perllib/FixMyStreet/App/Controller/Report/Update.pm b/perllib/FixMyStreet/App/Controller/Report/Update.pm
index c28039808..99eae8659 100644
--- a/perllib/FixMyStreet/App/Controller/Report/Update.pm
+++ b/perllib/FixMyStreet/App/Controller/Report/Update.pm
@@ -125,12 +125,19 @@ sub process_user : Private {
my $parsed = FixMyStreet::SMS->parse_username($params{username});
my $type = $parsed->{type} || 'email';
- $type = 'email' unless FixMyStreet->config('SMS_AUTHENTICATION');
+ $type = 'email' unless FixMyStreet->config('SMS_AUTHENTICATION') || $c->stash->{contributing_as_another_user};
$update->user( $c->model('DB::User')->find_or_new( { $type => $parsed->{username} } ) )
unless $update->user;
+ $c->stash->{phone_may_be_mobile} = $type eq 'phone' && $parsed->{may_be_mobile};
+
# The user is trying to sign in. We only care about username from the params.
if ( $c->get_param('submit_sign_in') || $c->get_param('password_sign_in') ) {
+ $c->stash->{tfa_data} = {
+ detach_to => '/report/update/report_update',
+ login_success => 1,
+ oauth_update => { $update->get_inflated_columns }
+ };
unless ( $c->forward( '/auth/sign_in', [ $params{username} ] ) ) {
$c->stash->{field_errors}->{password} = _('There was a problem with your login information. If you cannot remember your password, or do not have one, please fill in the &lsquo;No&rsquo; section of the form.');
return 1;
@@ -144,11 +151,14 @@ sub process_user : Private {
$update->user->name( Utils::trim_text( $params{name} ) )
if $params{name};
- $update->user->password( Utils::trim_text( $params{password_register} ) )
- if $params{password_register};
$update->user->title( Utils::trim_text( $params{fms_extra_title} ) )
if $params{fms_extra_title};
+ if ($params{password_register}) {
+ $c->forward('/auth/test_password', [ $params{password_register} ]);
+ $update->user->password(Utils::trim_text($params{password_register}));
+ }
+
return 1;
}
@@ -161,7 +171,9 @@ what we have so far.
sub oauth_callback : Private {
my ( $self, $c, $token_code ) = @_;
- $c->stash->{oauth_update} = $token_code;
+ my $auth_token = $c->forward('/tokens/load_auth_token',
+ [ $token_code, 'update/social' ]);
+ $c->stash->{oauth_update} = $auth_token->data;
$c->detach('report_update');
}
@@ -176,9 +188,7 @@ sub initialize_update : Private {
my $update;
if ($c->stash->{oauth_update}) {
- my $auth_token = $c->forward( '/tokens/load_auth_token',
- [ $c->stash->{oauth_update}, 'update/social' ] );
- $update = $c->model("DB::Comment")->new($auth_token->data);
+ $update = $c->model("DB::Comment")->new($c->stash->{oauth_update});
}
if ($update) {
@@ -356,6 +366,12 @@ sub check_for_errors : Private {
delete $field_errors{username};
}
+ # if we're contributing as someone else then allow landline numbers
+ if ( $field_errors{phone} && $c->stash->{contributing_as_another_user} && !$c->stash->{phone_may_be_mobile}) {
+ delete $field_errors{username};
+ delete $field_errors{phone};
+ }
+
if ( my $photo_error = delete $c->stash->{photo_error} ) {
$field_errors{photo} = $photo_error;
}
@@ -469,7 +485,7 @@ sub redirect_or_confirm_creation : Private {
if ( $update->confirmed ) {
$c->forward( 'update_problem' );
$c->forward( 'signup_for_alerts' );
- if ($c->stash->{contributing_as_another_user}) {
+ if ($c->stash->{contributing_as_another_user} && $update->user->email) {
$c->send_email( 'other-updated.txt', {
to => [ [ $update->user->email, $update->name ] ],
} );
@@ -478,6 +494,9 @@ sub redirect_or_confirm_creation : Private {
return 1;
}
+ # Superusers using 2FA can not log in by code
+ $c->detach( '/page_error_403_access_denied', [] ) if $update->user->has_2fa;
+
my $data = $c->stash->{token_data};
$data->{id} = $update->id;
$data->{add_alert} = $c->get_param('add_alert') ? 1 : 0;
diff --git a/perllib/FixMyStreet/App/Controller/Reports.pm b/perllib/FixMyStreet/App/Controller/Reports.pm
index ec7a192b3..7c3796c42 100644
--- a/perllib/FixMyStreet/App/Controller/Reports.pm
+++ b/perllib/FixMyStreet/App/Controller/Reports.pm
@@ -5,7 +5,6 @@ use namespace::autoclean;
use JSON::MaybeXS;
use List::MoreUtils qw(any);
use Path::Tiny;
-use POSIX qw(strcoll);
use RABX;
use mySociety::MaPit;
@@ -93,18 +92,8 @@ sub index : Path : Args(0) {
$c->stash->{children} = $children;
}
} else {
- # Fetch all bodies
- my @bodies = $c->model('DB::Body')->search({
- deleted => 0,
- }, {
- '+select' => [ { count => 'area_id' } ],
- '+as' => [ 'area_count' ],
- join => 'body_areas',
- distinct => 1,
- })->all;
- @bodies = sort { strcoll($a->name, $b->name) } @bodies;
+ my @bodies = $c->model('DB::Body')->active->translated->with_area_count->all_sorted;
$c->stash->{bodies} = \@bodies;
- $c->stash->{any_empty_bodies} = any { $_->get_column('area_count') == 0 } @bodies;
}
# Down here so that error pages aren't cached.
@@ -451,16 +440,13 @@ sub summary : Private {
# required to stop errors in generate_grouped_data
$c->stash->{q_state} = '';
- $c->stash->{ward} = $c->get_param('ward');
+ $c->stash->{ward} = $c->get_param('area');
$c->stash->{start_date} = $dtf->format_date($start_date);
$c->stash->{end_date} = $c->get_param('end_date');
$c->stash->{group_by_default} = 'category';
- my $area_id = $c->stash->{body}->body_areas->first->area_id;
- my $children = mySociety::MaPit::call('area/children', $area_id,
- type => $c->cobrand->area_types_children,
- );
+ my $children = $c->stash->{body}->first_area_children;
$c->stash->{children} = $children;
$c->forward('/admin/fetch_contacts');
@@ -508,7 +494,7 @@ sub export_summary_csv : Private {
'postcode',
'url',
],
- filename => 'fixmystreet-data.csv',
+ filename => 'fixmystreet-data',
};
$c->forward('/dashboard/generate_csv');
}
diff --git a/perllib/FixMyStreet/App/Controller/Rss.pm b/perllib/FixMyStreet/App/Controller/Rss.pm
index 7cf4783c0..e1da4445d 100755
--- a/perllib/FixMyStreet/App/Controller/Rss.pm
+++ b/perllib/FixMyStreet/App/Controller/Rss.pm
@@ -282,14 +282,15 @@ sub add_row : Private {
$item{pubDate} = $pubDate if $pubDate;
$item{category} = encode_entities($row->{category}) if $row->{category};
- if ($c->cobrand->allow_photo_display($row) && $row->{photo}) {
+ if ((my $photo_to_show = $c->cobrand->allow_photo_display($row)) && $row->{photo}) {
# Bit yucky as we don't have full objects here
my $photoset = FixMyStreet::App::Model::PhotoSet->new({ db_data => $row->{photo} });
- my $first_fn = $photoset->get_id(0);
+ my $idx = $photo_to_show - 1;
+ my $first_fn = $photoset->get_id($idx);
my ($hash, $format) = split /\./, $first_fn;
my $cachebust = substr($hash, 0, 8);
my $key = $alert_type->item_table eq 'comment' ? 'c/' : '';
- $item{description} .= encode_entities("\n<br><img src=\"". $base_url . "/photo/$key$row->{id}.0.$format?$cachebust\">");
+ $item{description} .= encode_entities("\n<br><img src=\"". $base_url . "/photo/$key$row->{id}.$idx.$format?$cachebust\">");
}
if ( $row->{used_map} ) {
diff --git a/perllib/FixMyStreet/App/Response.pm b/perllib/FixMyStreet/App/Response.pm
index 16ebf995f..6b32e6ebb 100644
--- a/perllib/FixMyStreet/App/Response.pm
+++ b/perllib/FixMyStreet/App/Response.pm
@@ -13,7 +13,7 @@ around 'redirect' => sub {
return $self->$orig() unless @_; # getter
my $agent = $self->_context->request->user_agent;
- return $self->$orig(@_) unless $agent =~ /Edge\/14/; # Only care about Edge
+ return $self->$orig(@_) unless $agent && $agent =~ /Edge\/14/; # Only care about Edge
# Instead of a redirect, output HTML that redirects
$self->body(<<END