Match users better by their private commit email
Private commit emails were introduced in !22560, but some parts of GitLab were not updated to take account of them. This commit adds support in places that were missed.
This commit is contained in:
parent
3eb366722e
commit
59c4fb4ecb
|
|
@ -230,24 +230,13 @@ class Commit
|
|||
|
||||
def lazy_author
|
||||
BatchLoader.for(author_email.downcase).batch do |emails, loader|
|
||||
# A Hash that maps user Emails to the corresponding User objects. The
|
||||
# Emails at this point are the _primary_ Emails of the Users.
|
||||
users_for_emails = User
|
||||
.by_any_email(emails)
|
||||
.each_with_object({}) { |user, hash| hash[user.email] = user }
|
||||
users = User.by_any_email(emails).includes(:emails)
|
||||
|
||||
users_for_ids = users_for_emails
|
||||
.values
|
||||
.each_with_object({}) { |user, hash| hash[user.id] = user }
|
||||
emails.each do |email|
|
||||
user = users.find { |u| u.any_email?(email) }
|
||||
|
||||
# Some commits may have used an alternative Email address. In this case we
|
||||
# need to query the "emails" table to map those addresses to User objects.
|
||||
Email
|
||||
.where(email: emails - users_for_emails.keys)
|
||||
.pluck(:email, :user_id)
|
||||
.each { |(email, id)| users_for_emails[email] = users_for_ids[id] }
|
||||
|
||||
users_for_emails.each { |email, user| loader.call(email, user) }
|
||||
loader.call(email, user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -349,20 +349,28 @@ class User < ActiveRecord::Base
|
|||
def find_by_any_email(email, confirmed: false)
|
||||
return unless email
|
||||
|
||||
downcased = email.downcase
|
||||
|
||||
find_by_private_commit_email(downcased) || by_any_email(downcased, confirmed: confirmed).take
|
||||
by_any_email(email, confirmed: confirmed).take
|
||||
end
|
||||
|
||||
# Returns a relation containing all the users for the given Email address
|
||||
def by_any_email(email, confirmed: false)
|
||||
users = where(email: email)
|
||||
users = users.confirmed if confirmed
|
||||
# Returns a relation containing all the users for the given email addresses
|
||||
#
|
||||
# @param emails [String, Array<String>] email addresses to check
|
||||
# @param confirmed [Boolean] Only return users where the email is confirmed
|
||||
def by_any_email(emails, confirmed: false)
|
||||
emails = Array(emails).map(&:downcase)
|
||||
|
||||
emails = joins(:emails).where(emails: { email: email })
|
||||
emails = emails.confirmed if confirmed
|
||||
from_users = where(email: emails)
|
||||
from_users = from_users.confirmed if confirmed
|
||||
|
||||
from_union([users, emails])
|
||||
from_emails = joins(:emails).where(emails: { email: emails })
|
||||
from_emails = from_emails.confirmed if confirmed
|
||||
|
||||
items = [from_users, from_emails]
|
||||
|
||||
user_ids = Gitlab::PrivateCommitEmail.user_ids_for_emails(emails)
|
||||
items << where(id: user_ids) if user_ids.present?
|
||||
|
||||
from_union(items)
|
||||
end
|
||||
|
||||
def find_by_private_commit_email(email)
|
||||
|
|
@ -1031,6 +1039,7 @@ class User < ActiveRecord::Base
|
|||
def all_emails
|
||||
all_emails = []
|
||||
all_emails << email unless temp_oauth_email?
|
||||
all_emails << private_commit_email
|
||||
all_emails.concat(emails.map(&:email))
|
||||
all_emails
|
||||
end
|
||||
|
|
@ -1043,16 +1052,24 @@ class User < ActiveRecord::Base
|
|||
verified_emails
|
||||
end
|
||||
|
||||
def any_email?(check_email)
|
||||
downcased = check_email.downcase
|
||||
|
||||
# handle the outdated private commit email case
|
||||
return true if persisted? &&
|
||||
id == Gitlab::PrivateCommitEmail.user_id_for_email(downcased)
|
||||
|
||||
all_emails.include?(check_email.downcase)
|
||||
end
|
||||
|
||||
def verified_email?(check_email)
|
||||
downcased = check_email.downcase
|
||||
|
||||
if email == downcased
|
||||
primary_email_verified?
|
||||
else
|
||||
user_id = Gitlab::PrivateCommitEmail.user_id_for_email(downcased)
|
||||
# handle the outdated private commit email case
|
||||
return true if persisted? &&
|
||||
id == Gitlab::PrivateCommitEmail.user_id_for_email(downcased)
|
||||
|
||||
user_id == id || emails.confirmed.where(email: downcased).exists?
|
||||
end
|
||||
verified_emails.include?(check_email.downcase)
|
||||
end
|
||||
|
||||
def hook_attrs
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ module Gitlab
|
|||
match[:id].to_i
|
||||
end
|
||||
|
||||
def user_ids_for_emails(emails)
|
||||
emails.map { |email| user_id_for_email(email) }.compact.uniq
|
||||
end
|
||||
|
||||
def for_user(user)
|
||||
hostname = Gitlab::CurrentSettings.current_application_settings.commit_email_hostname
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ require 'spec_helper'
|
|||
|
||||
describe Gitlab::PrivateCommitEmail do
|
||||
let(:hostname) { Gitlab::CurrentSettings.current_application_settings.commit_email_hostname }
|
||||
let(:id) { 1 }
|
||||
let(:valid_email) { "#{id}-foo@#{hostname}" }
|
||||
let(:invalid_email) { "#{id}-foo@users.noreply.bar.com" }
|
||||
|
||||
context '.regex' do
|
||||
subject { described_class.regex }
|
||||
|
|
@ -16,18 +19,25 @@ describe Gitlab::PrivateCommitEmail do
|
|||
end
|
||||
|
||||
context '.user_id_for_email' do
|
||||
let(:id) { 1 }
|
||||
|
||||
it 'parses user id from email' do
|
||||
email = "#{id}-foo@#{hostname}"
|
||||
|
||||
expect(described_class.user_id_for_email(email)).to eq(id)
|
||||
expect(described_class.user_id_for_email(valid_email)).to eq(id)
|
||||
end
|
||||
|
||||
it 'returns nil on invalid commit email' do
|
||||
email = "#{id}-foo@users.noreply.bar.com"
|
||||
expect(described_class.user_id_for_email(invalid_email)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
expect(described_class.user_id_for_email(email)).to be_nil
|
||||
context '.user_ids_for_email' do
|
||||
it 'returns deduplicated user IDs for each valid email' do
|
||||
result = described_class.user_ids_for_emails([valid_email, valid_email, invalid_email])
|
||||
|
||||
expect(result).to eq([id])
|
||||
end
|
||||
|
||||
it 'returns an empty array with no valid emails' do
|
||||
result = described_class.user_ids_for_emails([invalid_email])
|
||||
expect(result).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ describe Commit do
|
|||
context 'using eager loading' do
|
||||
let!(:alice) { create(:user, email: 'alice@example.com') }
|
||||
let!(:bob) { create(:user, email: 'hunter2@example.com') }
|
||||
let!(:jeff) { create(:user) }
|
||||
|
||||
let(:alice_commit) do
|
||||
described_class.new(RepoHelpers.sample_commit, project).tap do |c|
|
||||
|
|
@ -93,7 +94,14 @@ describe Commit do
|
|||
end
|
||||
end
|
||||
|
||||
let!(:commits) { [alice_commit, bob_commit, eve_commit] }
|
||||
let(:jeff_commit) do
|
||||
# The commit for Jeff uses his private commit email
|
||||
described_class.new(RepoHelpers.sample_commit, project).tap do |c|
|
||||
c.author_email = jeff.private_commit_email
|
||||
end
|
||||
end
|
||||
|
||||
let!(:commits) { [alice_commit, bob_commit, eve_commit, jeff_commit] }
|
||||
|
||||
before do
|
||||
create(:email, user: bob, email: 'bob@example.com')
|
||||
|
|
@ -125,6 +133,20 @@ describe Commit do
|
|||
expect(bob_commit.author).to eq(bob)
|
||||
end
|
||||
|
||||
it "preloads the authors for Commits using a User's private commit Email" do
|
||||
commits.each(&:lazy_author)
|
||||
|
||||
expect(jeff_commit.author).to eq(jeff)
|
||||
end
|
||||
|
||||
it "preloads the authors for Commits using a User's outdated private commit Email" do
|
||||
jeff.update!(username: 'new-username')
|
||||
|
||||
commits.each(&:lazy_author)
|
||||
|
||||
expect(jeff_commit.author).to eq(jeff)
|
||||
end
|
||||
|
||||
it 'sets the author to Nil if an author could not be found for a Commit' do
|
||||
commits.each(&:lazy_author)
|
||||
|
||||
|
|
|
|||
|
|
@ -1174,6 +1174,22 @@ describe User do
|
|||
|
||||
expect(described_class.by_any_email(user.email, confirmed: true)).to eq([user])
|
||||
end
|
||||
|
||||
it 'finds user through a private commit email' do
|
||||
user = create(:user)
|
||||
private_email = user.private_commit_email
|
||||
|
||||
expect(described_class.by_any_email(private_email)).to eq([user])
|
||||
expect(described_class.by_any_email(private_email, confirmed: true)).to eq([user])
|
||||
end
|
||||
|
||||
it 'finds user through a private commit email in an array' do
|
||||
user = create(:user)
|
||||
private_email = user.private_commit_email
|
||||
|
||||
expect(described_class.by_any_email([private_email])).to eq([user])
|
||||
expect(described_class.by_any_email([private_email], confirmed: true)).to eq([user])
|
||||
end
|
||||
end
|
||||
|
||||
describe '.search' do
|
||||
|
|
@ -1501,7 +1517,12 @@ describe User do
|
|||
email_unconfirmed = create :email, user: user
|
||||
user.reload
|
||||
|
||||
expect(user.all_emails).to match_array([user.email, email_unconfirmed.email, email_confirmed.email])
|
||||
expect(user.all_emails).to contain_exactly(
|
||||
user.email,
|
||||
user.private_commit_email,
|
||||
email_unconfirmed.email,
|
||||
email_confirmed.email
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -1512,7 +1533,11 @@ describe User do
|
|||
email_confirmed = create :email, user: user, confirmed_at: Time.now
|
||||
create :email, user: user
|
||||
|
||||
expect(user.verified_emails).to match_array([user.email, user.private_commit_email, email_confirmed.email])
|
||||
expect(user.verified_emails).to contain_exactly(
|
||||
user.email,
|
||||
user.private_commit_email,
|
||||
email_confirmed.email
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -1532,6 +1557,14 @@ describe User do
|
|||
expect(user.verified_email?(user.private_commit_email)).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns true for an outdated private commit email' do
|
||||
old_email = user.private_commit_email
|
||||
|
||||
user.update!(username: 'changed-username')
|
||||
|
||||
expect(user.verified_email?(old_email)).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false when the email is not verified/confirmed' do
|
||||
email_unconfirmed = create :email, user: user
|
||||
user.reload
|
||||
|
|
|
|||
Loading…
Reference in New Issue