DEV: Introduce Auth::Result API for overrides_* (#15378)

This allows authenticators to instruct the Auth::Result to override attributes without using the general site settings. This provides an easy migration path for auth plugins which offer their own "overrides email", "overrides username" or "overrides name" settings. With this new api, they can set `overrides_*` on the result object, and the attribute will be overriden regardless of the general site setting.

ManagedAuthenticator is updated to use this new API. Plugins which consume ManagedAuthenticator will instantly take advantage of this change.
This commit is contained in:
David Taylor 2021-12-23 10:53:17 +00:00 committed by GitHub
parent b705971d42
commit cdf4d7156e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 83 additions and 50 deletions

View File

@ -77,17 +77,6 @@ class Auth::ManagedAuthenticator < Auth::Authenticator
# Save to the DB. Do this even if we don't have a user - it might be linked up later in after_create_account # Save to the DB. Do this even if we don't have a user - it might be linked up later in after_create_account
association.save! association.save!
# Update the user's email address from the auth payload
if association.user &&
(always_update_user_email? || association.user.email.end_with?(".invalid")) &&
primary_email_verified?(auth_token) &&
(email = auth_token.dig(:info, :email)) &&
(email != association.user.email) &&
!User.find_by_email(email)
association.user.update!(email: email)
end
# Update avatar/profile # Update avatar/profile
retrieve_avatar(association.user, association.info["image"]) retrieve_avatar(association.user, association.info["image"])
retrieve_profile(association.user, association.info) retrieve_profile(association.user, association.info)
@ -104,6 +93,7 @@ class Auth::ManagedAuthenticator < Auth::Authenticator
end end
result.username = info[:nickname] result.username = info[:nickname]
result.email_valid = primary_email_verified?(auth_token) if result.email.present? result.email_valid = primary_email_verified?(auth_token) if result.email.present?
result.overrides_email = always_update_user_email?
result.extra_data = { result.extra_data = {
provider: auth_token[:provider], provider: auth_token[:provider],
uid: auth_token[:uid] uid: auth_token[:uid]

View File

@ -15,14 +15,16 @@ class Auth::Result
:requires_invite, :requires_invite,
:not_allowed_from_ip_address, :not_allowed_from_ip_address,
:admin_not_allowed_from_ip_address, :admin_not_allowed_from_ip_address,
:omit_username, # Used by plugins to prevent username edits
:skip_email_validation, :skip_email_validation,
:destination_url, :destination_url,
:omniauth_disallow_totp, :omniauth_disallow_totp,
:failed, :failed,
:failed_reason, :failed_reason,
:failed_code, :failed_code,
:associated_groups :associated_groups,
:overrides_email,
:overrides_username,
:overrides_name,
] ]
attr_accessor *ATTRIBUTES attr_accessor *ATTRIBUTES
@ -33,12 +35,14 @@ class Auth::Result
:email, :email,
:username, :username,
:email_valid, :email_valid,
:omit_username,
:name, :name,
:authenticator_name, :authenticator_name,
:extra_data, :extra_data,
:skip_email_validation, :skip_email_validation,
:associated_groups :associated_groups,
:overrides_email,
:overrides_username,
:overrides_name,
] ]
def [](key) def [](key)
@ -79,16 +83,19 @@ class Auth::Result
def apply_user_attributes! def apply_user_attributes!
change_made = false change_made = false
if SiteSetting.auth_overrides_username? && username.present? if (SiteSetting.auth_overrides_username? || overrides_username) && username.present?
change_made = UsernameChanger.override(user, username) change_made = UsernameChanger.override(user, username)
end end
if SiteSetting.auth_overrides_email && email_valid && email.present? && user.email != Email.downcase(email) if (SiteSetting.auth_overrides_email || overrides_email || user&.email&.ends_with?(".invalid")) &&
email_valid &&
email.present? &&
user.email != Email.downcase(email)
user.email = email user.email = email
change_made = true change_made = true
end end
if SiteSetting.auth_overrides_name && name.present? && user.name != name if (SiteSetting.auth_overrides_name || overrides_name) && name.present? && user.name != name
user.name = name user.name = name
change_made = true change_made = true
end end
@ -120,11 +127,11 @@ class Auth::Result
end end
def can_edit_name def can_edit_name
!SiteSetting.auth_overrides_name !(SiteSetting.auth_overrides_name || overrides_name)
end end
def can_edit_username def can_edit_username
!(SiteSetting.auth_overrides_username || omit_username) !(SiteSetting.auth_overrides_username || overrides_username)
end end
def to_client_hash def to_client_hash

View File

@ -222,35 +222,6 @@ describe Auth::ManagedAuthenticator do
end end
end end
describe "email update" do
fab!(:user) { Fabricate(:user) }
let!(:associated) { UserAssociatedAccount.create!(user: user, provider_name: 'myauth', provider_uid: "1234") }
it "updates the user's email if currently invalid" do
user.update!(email: "someemail@discourse.org")
# Existing email is valid, do not change
expect { result = authenticator.after_authenticate(hash) }
.not_to change { user.reload.email }
user.update!(email: "someemail@discourse.invalid")
# Existing email is invalid, expect change
expect { result = authenticator.after_authenticate(hash) }
.to change { user.reload.email }
expect(user.email).to eq("awesome@example.com")
end
it "doesn't raise error if email is taken" do
other_user = Fabricate(:user, email: "awesome@example.com")
user.update!(email: "someemail@discourse.invalid")
expect { result = authenticator.after_authenticate(hash) }
.not_to change { user.reload.email }
expect(user.email).to eq("someemail@discourse.invalid")
end
end
describe "avatar on create" do describe "avatar on create" do
fab!(:user) { Fabricate(:user) } fab!(:user) { Fabricate(:user) }
let!(:association) { UserAssociatedAccount.create!(provider_name: 'myauth', provider_uid: "1234") } let!(:association) { UserAssociatedAccount.create!(provider_name: 'myauth', provider_uid: "1234") }

View File

@ -0,0 +1,65 @@
# frozen_string_literal: true
require 'rails_helper'
describe Auth::Result do
fab!(:initial_email) { "initialemail@example.org" }
fab!(:initial_username) { "initialusername" }
fab!(:initial_name) { "Initial Name" }
fab!(:user) { Fabricate(:user, email: initial_email, username: initial_username, name: initial_name) }
let(:new_email) { "newemail@example.org" }
let(:new_username) { "newusername" }
let(:new_name) { "New Name" }
let(:result) do
result = Auth::Result.new
result.email = new_email
result.username = new_username
result.name = new_name
result.user = user
result.email_valid = true
result
end
it "doesn't override user attributes by default" do
result.apply_user_attributes!
expect(user.email).to eq(initial_email)
expect(user.username).to eq(initial_username)
expect(user.name).to eq(initial_name)
end
it "overrides user attributes when site settings enabled" do
SiteSetting.email_editable = false
SiteSetting.auth_overrides_email = true
SiteSetting.auth_overrides_name = true
SiteSetting.auth_overrides_username = true
result.apply_user_attributes!
expect(user.email).to eq(new_email)
expect(user.username).to eq(new_username)
expect(user.name).to eq(new_name)
end
it "overrides user attributes when result attributes set" do
result.overrides_email = true
result.overrides_name = true
result.overrides_username = true
result.apply_user_attributes!
expect(user.email).to eq(new_email)
expect(user.username).to eq(new_username)
expect(user.name).to eq(new_name)
end
it "updates the user's email if currently invalid" do
user.update!(email: "someemail@discourse.org")
expect { result.apply_user_attributes! }.not_to change { user.email }
user.update!(email: "someemail@discourse.invalid")
expect { result.apply_user_attributes! }.to change { user.email }
expect(user.email).to eq(new_email)
end
end

View File

@ -411,7 +411,7 @@ RSpec.describe Users::OmniauthCallbacksController do
expect(user.confirm_password?("securepassword")).to eq(false) expect(user.confirm_password?("securepassword")).to eq(false)
end end
it "should update name/username/email when sso_overrides is enabled" do it "should update name/username/email when SiteSetting.auth_overrides_* are enabled" do
SiteSetting.email_editable = false SiteSetting.email_editable = false
SiteSetting.auth_overrides_email = true SiteSetting.auth_overrides_email = true
SiteSetting.auth_overrides_name = true SiteSetting.auth_overrides_name = true