FIX: Improve email validation error handling for external logins (#11307)

- Display reason for validation error when logging in via an authenticator
- Fix email validation handling for 'Discourse SSO', and add a spec

Previously, validation errors (e.g. blocked or already-taken emails) would raise a generic error with no useful information.
This commit is contained in:
David Taylor 2020-11-23 11:06:08 +00:00 committed by GitHub
parent 4638c6fc8d
commit 20c0da8516
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 62 additions and 15 deletions

View File

@ -244,7 +244,7 @@ class SessionController < ApplicationController
text = nil text = nil
# If there's a problem with the email we can explain that # If there's a problem with the email we can explain that
if (e.record.is_a?(User) && e.record.errors[:email].present?) if (e.record.is_a?(User) && e.record.errors[:primary_email].present?)
if e.record.email.blank? if e.record.email.blank?
text = I18n.t("sso.no_email") text = I18n.t("sso.no_email")
else else

View File

@ -67,13 +67,14 @@ class Users::OmniauthCallbacksController < ApplicationController
end end
@auth_result.destination_url = @origin @auth_result.destination_url = @origin
if @auth_result.failed?
flash[:error] = @auth_result.failed_reason.html_safe
render('failure')
else
@auth_result.authenticator_name = authenticator.name @auth_result.authenticator_name = authenticator.name
return render_auth_result_failure if @auth_result.failed?
complete_response_data complete_response_data
return render_auth_result_failure if @auth_result.failed?
cookies['_bypass_cache'] = true cookies['_bypass_cache'] = true
cookies[:authentication_data] = { cookies[:authentication_data] = {
value: @auth_result.to_client_hash.to_json, value: @auth_result.to_client_hash.to_json,
@ -81,7 +82,6 @@ class Users::OmniauthCallbacksController < ApplicationController
} }
redirect_to @origin redirect_to @origin
end end
end
def failure def failure
error_key = params[:message].to_s.gsub(/[^\w-]/, "") || "generic" error_key = params[:message].to_s.gsub(/[^\w-]/, "") || "generic"
@ -98,6 +98,11 @@ class Users::OmniauthCallbacksController < ApplicationController
protected protected
def render_auth_result_failure
flash[:error] = @auth_result.failed_reason.html_safe
render 'failure'
end
def complete_response_data def complete_response_data
if @auth_result.user if @auth_result.user
user_found(@auth_result.user) user_found(@auth_result.user)
@ -138,7 +143,14 @@ class Users::OmniauthCallbacksController < ApplicationController
elsif ScreenedIpAddress.block_admin_login?(user, request.remote_ip) elsif ScreenedIpAddress.block_admin_login?(user, request.remote_ip)
@auth_result.admin_not_allowed_from_ip_address = true @auth_result.admin_not_allowed_from_ip_address = true
elsif Guardian.new(user).can_access_forum? && user.active # log on any account that is active with forum access elsif Guardian.new(user).can_access_forum? && user.active # log on any account that is active with forum access
begin
user.save! if @auth_result.apply_user_attributes! user.save! if @auth_result.apply_user_attributes!
rescue ActiveRecord::RecordInvalid => e
@auth_result.failed = true
@auth_result.failed_reason = e.record.errors.full_messages.join(", ")
return
end
log_on_user(user) log_on_user(user)
Invite.invalidate_for_email(user.email) # invite link can't be used to log in anymore Invite.invalidate_for_email(user.email) # invite link can't be used to log in anymore
session[:authentication] = nil # don't carry around old auth info, perhaps move elsewhere session[:authentication] = nil # don't carry around old auth info, perhaps move elsewhere

View File

@ -387,6 +387,25 @@ RSpec.describe Users::OmniauthCallbacksController do
expect(user.email).to eq('email@example.com') expect(user.email).to eq('email@example.com')
end end
it "shows error when sso_overrides_email causes a validation error" do
SiteSetting.email_editable = false
SiteSetting.sso_overrides_email = true
UserAssociatedAccount.create!(provider_name: "google_oauth2", user_id: user.id, provider_uid: '123545')
google_email = user.email
user.update!(email: 'anotheremail@example.com')
Fabricate(:user, email: google_email) # Another user has the google account email
get "/auth/google_oauth2/callback"
expect(response.status).to eq(200)
expect(response.body).to include(I18n.t("errors.messages.taken"))
expect(session[:current_user_id]).to eq(nil)
user.reload
expect(user.email).to eq('anotheremail@example.com')
end
context 'when user has TOTP enabled' do context 'when user has TOTP enabled' do
before do before do
user.create_totp(enabled: true) user.create_totp(enabled: true)

View File

@ -580,6 +580,22 @@ RSpec.describe SessionController do
expect(response.body).to include(I18n.t('sso.blank_id_error')) expect(response.body).to include(I18n.t('sso.blank_id_error'))
end end
it 'can handle invalid sso email validation errors' do
SiteSetting.blocked_email_domains = "test.com"
sso = get_sso("/")
sso.email = "test@test.com"
sso.external_id = '123'
sso.username = 'sam'
messages = track_log_messages(level: Logger::WARN) do
get "/session/sso_login", params: Rack::Utils.parse_query(sso.payload), headers: headers
end
expect(messages.length).to eq(0)
expect(response.status).to eq(500)
expect(response.body).to include(I18n.t("sso.email_error", email: ERB::Util.html_escape("test@test.com")))
end
it 'can handle invalid sso external ids due to banned word' do it 'can handle invalid sso external ids due to banned word' do
sso = get_sso("/") sso = get_sso("/")
sso.email = "test@test.com" sso.email = "test@test.com"