aboutsummaryrefslogtreecommitdiffstats
path: root/perllib/FixMyStreet/App/Controller
diff options
context:
space:
mode:
Diffstat (limited to 'perllib/FixMyStreet/App/Controller')
-rw-r--r--perllib/FixMyStreet/App/Controller/Auth.pm30
-rw-r--r--perllib/FixMyStreet/App/Controller/Auth/Social.pm129
-rw-r--r--perllib/FixMyStreet/App/Controller/Report/New.pm15
-rw-r--r--perllib/FixMyStreet/App/Controller/Report/Update.pm15
4 files changed, 151 insertions, 38 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Auth.pm b/perllib/FixMyStreet/App/Controller/Auth.pm
index 50d11a10c..b2c001566 100644
--- a/perllib/FixMyStreet/App/Controller/Auth.pm
+++ b/perllib/FixMyStreet/App/Controller/Auth.pm
@@ -44,13 +44,12 @@ sub general : Path : Args(0) {
# decide which action to take
$c->detach('code_sign_in') if $clicked_sign_in_by_code || ($data_email && !$data_password);
- if (!$data_username && !$data_password && !$data_email) {
- $c->detach('social/facebook_sign_in') if $c->get_param('facebook_sign_in');
- $c->detach('social/twitter_sign_in') if $c->get_param('twitter_sign_in');
+ if (!$data_username && !$data_password && !$data_email && $c->get_param('social_sign_in')) {
+ $c->forward('social/handle_sign_in');
}
- $c->forward( 'sign_in', [ $data_username ] )
- && $c->detach( 'redirect_on_signin', [ $c->get_param('r') ] );
+ $c->forward( 'sign_in', [ $data_username ] )
+ && $c->detach( 'redirect_on_signin', [ $c->get_param('r') ] );
}
@@ -180,13 +179,13 @@ sub email_sign_in : Private {
name => $c->get_param('name'),
password => $user->password,
};
- $token_data->{name} = $c->session->{oauth}{name}
- if $c->get_param('oauth_need_email') && $c->session->{oauth}{name} && !$token_data->{name};
- $token_data->{facebook_id} = $c->session->{oauth}{facebook_id}
- if $c->get_param('oauth_need_email') && $c->session->{oauth}{facebook_id};
- $token_data->{twitter_id} = $c->session->{oauth}{twitter_id}
- if $c->get_param('oauth_need_email') && $c->session->{oauth}{twitter_id};
+ if ($c->get_param('oauth_need_email')) {
+ $token_data->{name} = $c->session->{oauth}{name}
+ if $c->session->{oauth}{name} && !$token_data->{name};
+ $c->forward('set_oauth_token_data', [ $token_data ]);
+ }
+
if ($c->stash->{current_user}) {
$token_data->{old_user_id} = $c->stash->{current_user}->id;
$token_data->{r} = 'auth/change_email/success';
@@ -217,6 +216,14 @@ sub get_token : Private {
return $data;
}
+sub set_oauth_token_data : Private {
+ my ( $self, $c, $token_data ) = @_;
+
+ foreach (qw/facebook_id twitter_id oidc_id/) {
+ $token_data->{$_} = $c->session->{oauth}{$_} if $c->session->{oauth}{$_};
+ }
+}
+
=head2 token
Handle the 'email_sign_in' tokens. Find the account for the email address
@@ -275,6 +282,7 @@ sub process_login : Private {
$user->password( $data->{password}, 1 ) if $data->{password};
$user->facebook_id( $data->{facebook_id} ) if $data->{facebook_id};
$user->twitter_id( $data->{twitter_id} ) if $data->{twitter_id};
+ $user->add_oidc_id( $data->{oidc_id} ) if $data->{oidc_id};
$user->update_or_insert;
$c->authenticate( { $type => $data->{$type}, $ver => 1 }, 'no_password' );
diff --git a/perllib/FixMyStreet/App/Controller/Auth/Social.pm b/perllib/FixMyStreet/App/Controller/Auth/Social.pm
index d1a2c37cb..e0abf5c60 100644
--- a/perllib/FixMyStreet/App/Controller/Auth/Social.pm
+++ b/perllib/FixMyStreet/App/Controller/Auth/Social.pm
@@ -6,6 +6,7 @@ BEGIN { extends 'Catalyst::Controller'; }
use Net::Facebook::Oauth2;
use Net::Twitter::Lite::WithAPIv1_1;
+use OIDC::Lite::Client::WebServer::Azure;
=head1 NAME
@@ -13,10 +14,26 @@ FixMyStreet::App::Controller::Auth::Social - Catalyst Controller
=head1 DESCRIPTION
-Controller for the Facebook/Twitter authentication.
+Controller for the Facebook/Twitter/OpenID Connect authentication.
=head1 METHODS
+=head2 handle_sign_in
+
+Forwards to the appropriate (facebook|twitter|oidc)_sign_in method
+based on the social_sign_in parameter
+
+=cut
+
+sub handle_sign_in : Private {
+ my ($self, $c) = @_;
+
+ $c->detach('facebook_sign_in') if $c->get_param('social_sign_in') eq 'facebook';
+ $c->detach('twitter_sign_in') if $c->get_param('social_sign_in') eq 'twitter';
+ $c->detach('oidc_sign_in') if $c->get_param('social_sign_in') eq 'oidc';
+
+}
+
=head2 facebook_sign_in
Starts the Facebook authentication sequence.
@@ -142,6 +159,84 @@ sub twitter_callback: Path('/auth/Twitter') : Args(0) {
$c->forward('oauth_success', [ 'twitter', $info->{id}, $info->{name} ]);
}
+sub oidc : Private {
+ my ($self, $c) = @_;
+
+ my $config = $c->cobrand->feature('oidc_login');
+
+ OIDC::Lite::Client::WebServer::Azure->new(
+ id => $config->{client_id},
+ secret => $config->{secret},
+ authorize_uri => $config->{auth_uri},
+ access_token_uri => $config->{token_uri},
+ );
+}
+
+sub oidc_sign_in : Private {
+ my ( $self, $c ) = @_;
+
+ $c->detach( '/page_error_403_access_denied', [] ) if FixMyStreet->config('SIGNUPS_DISABLED');
+ $c->detach( '/page_error_400_bad_request', [] ) unless $c->cobrand->feature('oidc_login');
+
+ my $oidc = $c->forward('oidc');
+ my $url = $oidc->uri_to_redirect(
+ redirect_uri => $c->uri_for('/auth/OIDC'),
+ scope => 'openid',
+ state => 'test',
+ extra => {
+ response_mode => 'form_post',
+ },
+ );
+
+ my %oauth;
+ $oauth{return_url} = $c->get_param('r');
+ $oauth{detach_to} = $c->stash->{detach_to};
+ $oauth{detach_args} = $c->stash->{detach_args};
+ $c->session->{oauth} = \%oauth;
+ $c->res->redirect($url);
+}
+
+sub oidc_callback: Path('/auth/OIDC') : Args(0) {
+ my ( $self, $c ) = @_;
+
+ $c->detach('oauth_failure') if $c->get_param('error');
+ $c->detach('/page_error_400_bad_request', []) unless $c->get_param('code');
+
+ my $oidc = $c->forward('oidc');
+
+ my $id_token;
+ eval {
+ $id_token = $oidc->get_access_token(
+ code => $c->get_param('code'),
+ );
+ };
+ if ($@) {
+ (my $message = $@) =~ s/at [^ ]*Auth.pm.*//;
+ $c->detach('/page_error_500_internal_error', [ $message ]);
+ }
+
+ $c->detach('oauth_failure') unless $id_token;
+
+ # sanity check the token audience is us...
+ $c->detach('/page_error_500_internal_error', ['invalid id_token']) unless $id_token->payload->{aud} eq $c->cobrand->feature('oidc_login')->{client_id};
+
+ # Some claims need parsing into a friendlier format
+ # XXX check how much of this is Westminster/Azure-specific
+ my $name = join(" ", $id_token->payload->{given_name}, $id_token->payload->{family_name});
+ my $email = $id_token->payload->{email};
+ # WCC Azure provides a single email address as an array for some reason
+ my $emails = $id_token->payload->{emails};
+ if ($emails && @$emails) {
+ $email = $emails->[0];
+ }
+
+ # There's a chance that a user may have multiple OIDC logins, so build a namespaced uid to prevent collisions
+ my $uid = join(":", $c->cobrand->moniker, $c->cobrand->feature('oidc_login')->{client_id}, $id_token->payload->{sub});
+
+ $c->forward('oauth_success', [ 'oidc', $uid, $name, $email ]);
+}
+
+
sub oauth_failure : Private {
my ( $self, $c ) = @_;
@@ -159,19 +254,39 @@ sub oauth_success : Private {
my $user;
if ($email) {
- # Only Facebook gets here
+ # Only Facebook & OIDC get here
# We've got an ID and an email address
+
# Remove any existing mention of this ID
- my $existing = $c->model('DB::User')->find( { facebook_id => $uid } );
- $existing->update( { facebook_id => undef } ) if $existing;
- # Get or create a user, give it this Facebook ID
+ my $existing;
+ if ($type eq 'facebook') {
+ $existing = $c->model('DB::User')->find( { $type . '_id' => $uid } );
+ $existing->update( { $type . '_id' => undef } ) if $existing;
+ } elsif ( $type eq 'oidc' ) {
+ $existing = $c->model('DB::User')->find( { oidc_ids => \[
+ '&& ?', [ oidc_ids => [ $uid ] ]
+ ] } );
+ $existing->remove_oidc_id( $uid ) if $existing;
+ }
+
+ # Get or create a user, give it this Facebook/OIDC ID
$user = $c->model('DB::User')->find_or_new( { email => $email } );
- $user->facebook_id($uid);
+ if ( $type eq 'facebook' ) {
+ $user->facebook_id($uid);
+ } elsif ( $type eq 'oidc' ) {
+ $user->add_oidc_id($uid);
+ }
$user->name($name);
$user->in_storage() ? $user->update : $user->insert;
} else {
# We've got an ID, but no email
- $user = $c->model('DB::User')->find( { $type . '_id' => $uid } );
+ if ($type eq 'oidc') {
+ $user = $c->model('DB::User')->find( { oidc_ids => \[
+ '&& ?', [ oidc_ids => [ $uid ] ]
+ ] } );
+ } else {
+ $user = $c->model('DB::User')->find( { $type . '_id' => $uid } );
+ }
if ($user) {
# Matching ID in our database
$user->name($name);
diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm
index 120467905..7a3e94f95 100644
--- a/perllib/FixMyStreet/App/Controller/Report/New.pm
+++ b/perllib/FixMyStreet/App/Controller/Report/New.pm
@@ -1155,7 +1155,7 @@ sub check_for_errors : Private {
}
# if using social login then we don't care about other errors
- $c->stash->{is_social_user} = $c->get_param('facebook_sign_in') || $c->get_param('twitter_sign_in');
+ $c->stash->{is_social_user} = $c->get_param('social_sign_in') ? 1 : 0;
if ( $c->stash->{is_social_user} ) {
delete $field_errors{name};
delete $field_errors{username};
@@ -1197,10 +1197,8 @@ sub tokenize_user : Private {
password => $report->user->password,
title => $report->user->title,
};
- $c->stash->{token_data}{facebook_id} = $c->session->{oauth}{facebook_id}
- if $c->get_param('oauth_need_email') && $c->session->{oauth}{facebook_id};
- $c->stash->{token_data}{twitter_id} = $c->session->{oauth}{twitter_id}
- if $c->get_param('oauth_need_email') && $c->session->{oauth}{twitter_id};
+ $c->forward('/auth/set_oauth_token_data', [ $c->stash->{token_data} ])
+ if $c->get_param('oauth_need_email');
}
sub send_problem_confirm_email : Private {
@@ -1308,6 +1306,7 @@ sub process_confirmation : Private {
for (qw(name title facebook_id twitter_id)) {
$problem->user->$_( $data->{$_} ) if $data->{$_};
}
+ $problem->user->add_oidc_id($data->{oidc_id}) if $data->{oidc_id};
$problem->user->update;
}
if ($problem->user->email_verified) {
@@ -1368,11 +1367,7 @@ sub save_user_and_report : Private {
$c->stash->{detach_to} = '/report/new/oauth_callback';
$c->stash->{detach_args} = [$token->token];
- if ( $c->get_param('facebook_sign_in') ) {
- $c->detach('/auth/social/facebook_sign_in');
- } elsif ( $c->get_param('twitter_sign_in') ) {
- $c->detach('/auth/social/twitter_sign_in');
- }
+ $c->forward('/auth/social/handle_sign_in') if $c->get_param('social_sign_in');
}
# Save or update the user if appropriate
diff --git a/perllib/FixMyStreet/App/Controller/Report/Update.pm b/perllib/FixMyStreet/App/Controller/Report/Update.pm
index cbedf7a01..68c8a41ce 100644
--- a/perllib/FixMyStreet/App/Controller/Report/Update.pm
+++ b/perllib/FixMyStreet/App/Controller/Report/Update.pm
@@ -366,7 +366,7 @@ sub check_for_errors : Private {
);
# if using social login then we don't care about name and email errors
- $c->stash->{is_social_user} = $c->get_param('facebook_sign_in') || $c->get_param('twitter_sign_in');
+ $c->stash->{is_social_user} = $c->get_param('social_sign_in') ? 1 : 0;
if ( $c->stash->{is_social_user} ) {
delete $field_errors{name};
delete $field_errors{username};
@@ -404,10 +404,8 @@ sub tokenize_user : Private {
name => $update->user->name,
password => $update->user->password,
};
- $c->stash->{token_data}{facebook_id} = $c->session->{oauth}{facebook_id}
- if $c->get_param('oauth_need_email') && $c->session->{oauth}{facebook_id};
- $c->stash->{token_data}{twitter_id} = $c->session->{oauth}{twitter_id}
- if $c->get_param('oauth_need_email') && $c->session->{oauth}{twitter_id};
+ $c->forward('/auth/set_oauth_token_data', [ $c->stash->{token_data} ])
+ if $c->get_param('oauth_need_email');
}
=head2 save_update
@@ -440,11 +438,7 @@ sub save_update : Private {
$c->stash->{detach_to} = '/report/update/oauth_callback';
$c->stash->{detach_args} = [$token->token];
- if ( $c->get_param('facebook_sign_in') ) {
- $c->detach('/auth/social/facebook_sign_in');
- } elsif ( $c->get_param('twitter_sign_in') ) {
- $c->detach('/auth/social/twitter_sign_in');
- }
+ $c->forward('/auth/social/handle_sign_in') if $c->get_param('social_sign_in');
}
if ( $c->cobrand->never_confirm_updates ) {
@@ -585,6 +579,7 @@ sub process_confirmation : Private {
for (qw(name facebook_id twitter_id)) {
$comment->user->$_( $data->{$_} ) if $data->{$_};
}
+ $comment->user->add_oidc_id($data->{oidc_id}) if $data->{oidc_id};
$comment->user->password( $data->{password}, 1 ) if $data->{password};
$comment->user->update;
}