Skip to content
Snippets Groups Projects
Verified Commit 59c4fb4e authored by Nick Thomas's avatar Nick Thomas
Browse files

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.
parent 3eb36672
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -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_for_ids = users_for_emails
.values
.each_with_object({}) { |user, hash| hash[user.id] = user }
# 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) }
users = User.by_any_email(emails).includes(:emails)
emails.each do |email|
user = users.find { |u| u.any_email?(email) }
loader.call(email, user)
end
end
end
 
Loading
Loading
Loading
Loading
@@ -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)
from_users = where(email: emails)
from_users = from_users.confirmed if confirmed
 
emails = joins(:emails).where(emails: { email: email })
emails = emails.confirmed if confirmed
from_emails = joins(:emails).where(emails: { email: emails })
from_emails = from_emails.confirmed if confirmed
 
from_union([users, emails])
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)
Loading
Loading
@@ -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
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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
 
Loading
Loading
Loading
Loading
@@ -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 }
Loading
Loading
@@ -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
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
 
expect(described_class.user_id_for_email(email)).to be_nil
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
 
Loading
Loading
Loading
Loading
@@ -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|
Loading
Loading
@@ -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')
Loading
Loading
@@ -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)
 
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
@@ -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
 
Loading
Loading
@@ -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
 
Loading
Loading
@@ -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
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment