Commit dc10e297 authored by Mayra Cabrera's avatar Mayra Cabrera

Merge branch 'add-primary-email-to-emails' into 'master'

Add primary email to emails upon confirmation

See merge request gitlab-org/gitlab!70872
parents 1f52a43b fd9c17ba
...@@ -29,7 +29,7 @@ class Email < ApplicationRecord ...@@ -29,7 +29,7 @@ class Email < ApplicationRecord
end end
def unique_email def unique_email
self.errors.add(:email, 'has already been taken') if User.exists?(email: self.email) self.errors.add(:email, 'has already been taken') if primary_email_of_another_user?
end end
def validate_email_format def validate_email_format
...@@ -40,4 +40,14 @@ class Email < ApplicationRecord ...@@ -40,4 +40,14 @@ class Email < ApplicationRecord
def update_invalid_gpg_signatures def update_invalid_gpg_signatures
user.update_invalid_gpg_signatures if confirmed? user.update_invalid_gpg_signatures if confirmed?
end end
def user_primary_email?
email.casecmp?(user.email)
end
private
def primary_email_of_another_user?
User.where(email: email).where.not(id: user_id).exists?
end
end end
...@@ -274,14 +274,21 @@ class User < ApplicationRecord ...@@ -274,14 +274,21 @@ class User < ApplicationRecord
after_update :username_changed_hook, if: :saved_change_to_username? after_update :username_changed_hook, if: :saved_change_to_username?
after_destroy :post_destroy_hook after_destroy :post_destroy_hook
after_destroy :remove_key_cache after_destroy :remove_key_cache
after_create :add_primary_email_to_emails!, if: :confirmed?
after_commit(on: :update) do after_commit(on: :update) do
if previous_changes.key?('email') if previous_changes.key?('email')
# Grab previous_email here since previous_changes changes after # Add the old primary email to Emails if not added already - this should be removed
# #update_emails_with_primary_email and #update_notification_email are called # after the background migration for MR https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70872/ has completed,
# as the primary email is now added to Emails upon confirmation
# Issue to remove that: https://gitlab.com/gitlab-org/gitlab/-/issues/344134
previous_confirmed_at = previous_changes.key?('confirmed_at') ? previous_changes['confirmed_at'][0] : confirmed_at previous_confirmed_at = previous_changes.key?('confirmed_at') ? previous_changes['confirmed_at'][0] : confirmed_at
previous_email = previous_changes[:email][0] previous_email = previous_changes[:email][0]
if previous_confirmed_at && !emails.exists?(email: previous_email)
# rubocop: disable CodeReuse/ServiceClass
Emails::CreateService.new(self, user: self, email: previous_email).execute(confirmed_at: previous_confirmed_at)
# rubocop: enable CodeReuse/ServiceClass
end
update_emails_with_primary_email(previous_confirmed_at, previous_email)
update_invalid_gpg_signatures update_invalid_gpg_signatures
end end
end end
...@@ -935,6 +942,8 @@ class User < ApplicationRecord ...@@ -935,6 +942,8 @@ class User < ApplicationRecord
end end
def unique_email def unique_email
return if errors.added?(:email, _('has already been taken'))
if !emails.exists?(email: email) && Email.exists?(email: email) if !emails.exists?(email: email) && Email.exists?(email: email)
errors.add(:email, _('has already been taken')) errors.add(:email, _('has already been taken'))
end end
...@@ -963,24 +972,6 @@ class User < ApplicationRecord ...@@ -963,24 +972,6 @@ class User < ApplicationRecord
skip_reconfirmation! if emails.confirmed.where(email: self.email).any? skip_reconfirmation! if emails.confirmed.where(email: self.email).any?
end end
# Note: the use of the Emails services will cause `saves` on the user object, running
# through the callbacks again and can have side effects, such as the `previous_changes`
# hash and `_was` variables getting munged.
# By using an `after_commit` instead of `after_update`, we avoid the recursive callback
# scenario, though it then requires us to use the `previous_changes` hash
# rubocop: disable CodeReuse/ServiceClass
def update_emails_with_primary_email(previous_confirmed_at, previous_email)
primary_email_record = emails.find_by(email: email)
Emails::DestroyService.new(self, user: self).execute(primary_email_record) if primary_email_record
# the original primary email was confirmed, and we want that to carry over. We don't
# have access to the original confirmation values at this point, so just set confirmed_at
Emails::CreateService.new(self, user: self, email: previous_email).execute(confirmed_at: previous_confirmed_at)
update_columns(confirmed_at: primary_email_record.confirmed_at) if primary_email_record&.confirmed_at
end
# rubocop: enable CodeReuse/ServiceClass
def update_invalid_gpg_signatures def update_invalid_gpg_signatures
gpg_keys.each(&:update_invalid_gpg_signatures) gpg_keys.each(&:update_invalid_gpg_signatures)
end end
...@@ -1389,7 +1380,7 @@ class User < ApplicationRecord ...@@ -1389,7 +1380,7 @@ class User < ApplicationRecord
all_emails << email unless temp_oauth_email? all_emails << email unless temp_oauth_email?
all_emails << private_commit_email if include_private_email all_emails << private_commit_email if include_private_email
all_emails.concat(emails.map(&:email)) all_emails.concat(emails.map(&:email))
all_emails all_emails.uniq
end end
def verified_emails(include_private_email: true) def verified_emails(include_private_email: true)
...@@ -1397,7 +1388,7 @@ class User < ApplicationRecord ...@@ -1397,7 +1388,7 @@ class User < ApplicationRecord
verified_emails << email if primary_email_verified? verified_emails << email if primary_email_verified?
verified_emails << private_commit_email if include_private_email verified_emails << private_commit_email if include_private_email
verified_emails.concat(emails.confirmed.pluck(:email)) verified_emails.concat(emails.confirmed.pluck(:email))
verified_emails verified_emails.uniq
end end
def public_verified_emails def public_verified_emails
...@@ -1978,6 +1969,25 @@ class User < ApplicationRecord ...@@ -1978,6 +1969,25 @@ class User < ApplicationRecord
ci_job_token_scope.present? ci_job_token_scope.present?
end end
# override from Devise::Confirmable
#
# Add the primary email to user.emails (or confirm it if it was already
# present) when the primary email is confirmed.
def confirm(*args)
saved = super(*args)
return false unless saved
email_to_confirm = self.emails.find_by(email: self.email)
if email_to_confirm.present?
email_to_confirm.confirm(*args)
else
add_primary_email_to_emails!
end
saved
end
protected protected
# override, from Devise::Validatable # override, from Devise::Validatable
...@@ -2018,6 +2028,12 @@ class User < ApplicationRecord ...@@ -2018,6 +2028,12 @@ class User < ApplicationRecord
'en' 'en'
end end
# rubocop: disable CodeReuse/ServiceClass
def add_primary_email_to_emails!
Emails::CreateService.new(self, user: self, email: self.email).execute(confirmed_at: self.confirmed_at)
end
# rubocop: enable CodeReuse/ServiceClass
def notification_email_verified def notification_email_verified
return if notification_email.blank? || temp_oauth_email? return if notification_email.blank? || temp_oauth_email?
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
module Emails module Emails
class DestroyService < ::Emails::BaseService class DestroyService < ::Emails::BaseService
def execute(email) def execute(email)
raise StandardError, 'Cannot delete primary email' if email.user_primary_email?
email.destroy && update_secondary_emails!(email.email) email.destroy && update_secondary_emails!(email.email)
end end
......
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
%span.badge.badge-muted.badge-pill.gl-badge.badge-info= s_('Profiles|Public email') %span.badge.badge-muted.badge-pill.gl-badge.badge-info= s_('Profiles|Public email')
- if @primary_email === current_user.notification_email_or_default - if @primary_email === current_user.notification_email_or_default
%span.badge.badge-muted.badge-pill.gl-badge.badge-info= s_('Profiles|Default notification email') %span.badge.badge-muted.badge-pill.gl-badge.badge-info= s_('Profiles|Default notification email')
- @emails.each do |email| - @emails.reject(&:user_primary_email?).each do |email|
%li{ data: { qa_selector: 'email_row_content' } } %li{ data: { qa_selector: 'email_row_content' } }
= render partial: 'shared/email_with_badge', locals: { email: email.email, verified: email.confirmed? } = render partial: 'shared/email_with_badge', locals: { email: email.email, verified: email.confirmed? }
%span.float-right %span.float-right
......
# frozen_string_literal: true
class ScheduleAddPrimaryEmailToEmailsIfUserConfirmed < Gitlab::Database::Migration[1.0]
INTERVAL = 2.minutes.to_i
BATCH_SIZE = 10_000
MIGRATION = 'AddPrimaryEmailToEmailsIfUserConfirmed'
disable_ddl_transaction!
class User < ActiveRecord::Base
include ::EachBatch
self.table_name = 'users'
self.inheritance_column = :_type_disabled
end
def up
queue_background_migration_jobs_by_range_at_intervals(
User,
MIGRATION,
INTERVAL,
batch_size: BATCH_SIZE,
track_jobs: true
)
end
def down
# intentionally blank
end
end
9cefd32c003a68752f257973a983f77215b02011b7ca792de06c0e92c2462745
\ No newline at end of file
...@@ -102,6 +102,8 @@ RSpec.describe Admin::UsersController do ...@@ -102,6 +102,8 @@ RSpec.describe Admin::UsersController do
end end
describe "POST #impersonate" do describe "POST #impersonate" do
let_it_be(:user) { create(:user) }
before do before do
stub_licensed_features(extended_audit_events: true) stub_licensed_features(extended_audit_events: true)
end end
......
...@@ -96,7 +96,7 @@ RSpec.describe SessionsController, :geo do ...@@ -96,7 +96,7 @@ RSpec.describe SessionsController, :geo do
context 'when OTP authentication fails' do context 'when OTP authentication fails' do
it_behaves_like 'an auditable failed authentication' do it_behaves_like 'an auditable failed authentication' do
let(:user) { create(:user, :two_factor) } let_it_be(:user) { create(:user, :two_factor) }
let(:operation) { authenticate_2fa(otp_attempt: 'invalid', otp_user_id: user.id) } let(:operation) { authenticate_2fa(otp_attempt: 'invalid', otp_user_id: user.id) }
let(:method) { 'OTP' } let(:method) { 'OTP' }
end end
...@@ -108,7 +108,7 @@ RSpec.describe SessionsController, :geo do ...@@ -108,7 +108,7 @@ RSpec.describe SessionsController, :geo do
end end
it_behaves_like 'an auditable failed authentication' do it_behaves_like 'an auditable failed authentication' do
let(:user) { create(:user, :two_factor_via_u2f) } let_it_be(:user) { create(:user, :two_factor_via_u2f) }
let(:operation) { authenticate_2fa(device_response: 'invalid', otp_user_id: user.id) } let(:operation) { authenticate_2fa(device_response: 'invalid', otp_user_id: user.id) }
let(:method) { 'U2F' } let(:method) { 'U2F' }
end end
...@@ -122,7 +122,7 @@ RSpec.describe SessionsController, :geo do ...@@ -122,7 +122,7 @@ RSpec.describe SessionsController, :geo do
end end
it_behaves_like 'an auditable failed authentication' do it_behaves_like 'an auditable failed authentication' do
let(:user) { create(:user, :two_factor_via_webauthn) } let_it_be(:user) { create(:user, :two_factor_via_webauthn) }
let(:operation) { authenticate_2fa(device_response: 'invalid', otp_user_id: user.id) } let(:operation) { authenticate_2fa(device_response: 'invalid', otp_user_id: user.id) }
let(:method) { 'WebAuthn' } let(:method) { 'WebAuthn' }
end end
......
...@@ -9,7 +9,7 @@ RSpec.describe PasswordsController do ...@@ -9,7 +9,7 @@ RSpec.describe PasswordsController do
stub_licensed_features(extended_audit_events: true) stub_licensed_features(extended_audit_events: true)
end end
let(:user) { create(:user) } let_it_be(:user) { create(:user) }
subject { post :create, params: { user: { email: user.email } } } subject { post :create, params: { user: { email: user.email } } }
......
...@@ -7,7 +7,7 @@ RSpec.describe Audit::Changes do ...@@ -7,7 +7,7 @@ RSpec.describe Audit::Changes do
describe '.audit_changes' do describe '.audit_changes' do
let(:current_user) { create(:user, name: 'Mickey Mouse') } let(:current_user) { create(:user, name: 'Mickey Mouse') }
let(:user) { create(:user, name: 'Donald Duck') } let!(:user) { create(:user, name: 'Donald Duck') }
let(:options) { { model: user } } let(:options) { { model: user } }
subject(:audit!) { foo_instance.audit_changes(:name, options) } subject(:audit!) { foo_instance.audit_changes(:name, options) }
......
...@@ -4,7 +4,8 @@ require 'spec_helper' ...@@ -4,7 +4,8 @@ require 'spec_helper'
RSpec.describe EE::Audit::ProjectChangesAuditor do RSpec.describe EE::Audit::ProjectChangesAuditor do
describe '.audit_changes' do describe '.audit_changes' do
let(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:project) do let(:project) do
create( create(
:project, :project,
......
...@@ -15,7 +15,7 @@ RSpec.describe Gitlab::UsageDataNonSqlMetrics do ...@@ -15,7 +15,7 @@ RSpec.describe Gitlab::UsageDataNonSqlMetrics do
described_class.uncached_data described_class.uncached_data
end end
expect(recorder.count).to eq(55) expect(recorder.count).to eq(57)
end end
end end
end end
...@@ -52,7 +52,7 @@ RSpec.describe API::AuditEvents do ...@@ -52,7 +52,7 @@ RSpec.describe API::AuditEvents do
end end
context 'when authenticated, as an admin' do context 'when authenticated, as an admin' do
let(:admin) { create(:admin) } let_it_be(:admin) { create(:admin) }
context 'audit events feature is not available' do context 'audit events feature is not available' do
it_behaves_like '403 response' do it_behaves_like '403 response' do
......
...@@ -28,7 +28,7 @@ RSpec.describe API::Repositories do ...@@ -28,7 +28,7 @@ RSpec.describe API::Repositories do
context 'when unauthenticated', 'and project is public' do context 'when unauthenticated', 'and project is public' do
it_behaves_like 'an auditable and successful request' do it_behaves_like 'an auditable and successful request' do
let(:project) { create(:project, :public, :repository) } let_it_be(:project) { create(:project, :public, :repository) }
let(:current_user) { nil } let(:current_user) { nil }
end end
end end
......
...@@ -62,6 +62,9 @@ RSpec.describe API::Users do ...@@ -62,6 +62,9 @@ RSpec.describe API::Users do
end end
context 'extended audit events' do context 'extended audit events' do
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
before do before do
stub_licensed_features(extended_audit_events: true) stub_licensed_features(extended_audit_events: true)
end end
......
...@@ -164,9 +164,14 @@ RSpec.describe SmartcardController, type: :request do ...@@ -164,9 +164,14 @@ RSpec.describe SmartcardController, type: :request do
end end
it 'logs audit event' do it 'logs audit event' do
audit_event_service = instance_double(AuditEventService) audit_event_service = instance_double(::AuditEventService)
expect(AuditEventService).to( # Creating a confirmed user also creates an email corresponding to the user primary email
expect(::AuditEventService).to(
receive(:new)
.with(instance_of(User), instance_of(User), action: :create).and_call_original)
expect(::AuditEventService).to(
receive(:new) receive(:new)
.with(instance_of(User), instance_of(User), with: auth_method, ip_address: '127.0.0.1') .with(instance_of(User), instance_of(User), with: auth_method, ip_address: '127.0.0.1')
.and_return(audit_event_service)) .and_return(audit_event_service))
......
...@@ -5,7 +5,8 @@ require 'spec_helper' ...@@ -5,7 +5,8 @@ require 'spec_helper'
RSpec.describe ::Applications::CreateService do RSpec.describe ::Applications::CreateService do
include TestRequestHelpers include TestRequestHelpers
let(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:group) { create(:group) } let(:group) { create(:group) }
let(:params) { attributes_for(:application) } let(:params) { attributes_for(:application) }
......
...@@ -3,12 +3,12 @@ ...@@ -3,12 +3,12 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Users::ApproveService do RSpec.describe Users::ApproveService do
let(:current_user) { create(:admin) } let_it_be(:current_user) { create(:admin) }
subject(:service) { described_class.new(current_user) } subject(:service) { described_class.new(current_user) }
describe '#execute', :enable_admin_mode do describe '#execute', :enable_admin_mode do
let(:user) { create(:user, :blocked_pending_approval) } let!(:user) { create(:user, :blocked_pending_approval) }
subject(:operation) { service.execute(user) } subject(:operation) { service.execute(user) }
......
...@@ -3,12 +3,12 @@ ...@@ -3,12 +3,12 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Users::BlockService do RSpec.describe Users::BlockService do
let(:current_user) { create(:admin) } let_it_be(:current_user) { create(:admin) }
subject(:service) { described_class.new(current_user) } subject(:service) { described_class.new(current_user) }
describe '#execute' do describe '#execute' do
let(:user) { create(:user) } let!(:user) { create(:user) }
subject(:operation) { service.execute(user) } subject(:operation) { service.execute(user) }
......
...@@ -3,7 +3,8 @@ ...@@ -3,7 +3,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Users::CreateService do RSpec.describe Users::CreateService do
let(:current_user) { create(:admin) } let_it_be(:current_user) { create(:admin) }
let(:params) do let(:params) do
{ {
name: 'John Doe', name: 'John Doe',
......
...@@ -3,12 +3,12 @@ ...@@ -3,12 +3,12 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Users::DestroyService do RSpec.describe Users::DestroyService do
let(:current_user) { create(:admin) } let_it_be(:current_user) { create(:admin) }
subject(:service) { described_class.new(current_user) } subject(:service) { described_class.new(current_user) }
describe '#execute' do describe '#execute' do
let(:user) { create(:user) } let!(:user) { create(:user) }
subject(:operation) { service.execute(user) } subject(:operation) { service.execute(user) }
......
...@@ -32,7 +32,7 @@ RSpec.describe Users::RejectService do ...@@ -32,7 +32,7 @@ RSpec.describe Users::RejectService do
end end
context 'when user does not have permission to reject another user' do context 'when user does not have permission to reject another user' do
let(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
it 'does not log any audit event' do it 'does not log any audit event' do
expect { reject_user }.not_to change { AuditEvent.count } expect { reject_user }.not_to change { AuditEvent.count }
......
...@@ -97,12 +97,14 @@ RSpec.describe Users::UpdateService do ...@@ -97,12 +97,14 @@ RSpec.describe Users::UpdateService do
context 'audit events' do context 'audit events' do
context 'licensed' do context 'licensed' do
let_it_be_with_reload(:user) { create(:user) }
before do before do
stub_licensed_features(admin_audit_log: true) stub_licensed_features(admin_audit_log: true)
end end
context 'updating administrator status' do context 'updating administrator status' do
let_it_be(:admin_user) { create(:admin) } let_it_be_with_reload(:admin_user) { create(:admin) }
it 'logs making a user an administrator' do it 'logs making a user an administrator' do
expect do expect do
...@@ -113,8 +115,9 @@ RSpec.describe Users::UpdateService do ...@@ -113,8 +115,9 @@ RSpec.describe Users::UpdateService do
end end
it 'logs making an administrator a user' do it 'logs making an administrator a user' do
admin = create(:admin)
expect do expect do
update_user_as(admin_user, create(:admin), admin: false) update_user_as(admin_user, admin, admin: false)
end.to change { AuditEvent.count }.by(1) end.to change { AuditEvent.count }.by(1)
expect(AuditEvent.last.present.action).to eq('Changed admin status from true to false') expect(AuditEvent.last.present.action).to eq('Changed admin status from true to false')
...@@ -170,7 +173,8 @@ RSpec.describe Users::UpdateService do ...@@ -170,7 +173,8 @@ RSpec.describe Users::UpdateService do
end end
context 'with an admin user' do context 'with an admin user' do
let!(:admin_user) { create(:admin) } let_it_be_with_reload(:admin_user) { create(:admin) }
let(:service) { described_class.new(admin_user, ActionController::Parameters.new(params).permit!) } let(:service) { described_class.new(admin_user, ActionController::Parameters.new(params).permit!) }
let(:params) do let(:params) do
{ name: 'John Doe', username: 'jduser', email: 'jd@example.com', password: 'mydummypass' } { name: 'John Doe', username: 'jduser', email: 'jd@example.com', password: 'mydummypass' }
......
...@@ -3,7 +3,8 @@ ...@@ -3,7 +3,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Emails::CreateService do RSpec.describe Emails::CreateService do
let(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:opts) { { email: 'new@email.com', user: user } } let(:opts) { { email: 'new@email.com', user: user } }
subject(:service) { described_class.new(user, opts) } subject(:service) { described_class.new(user, opts) }
......
...@@ -3,8 +3,9 @@ ...@@ -3,8 +3,9 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Keys::CreateService do RSpec.describe Keys::CreateService do
let(:admin) { create(:admin) } let_it_be(:admin) { create(:admin) }
let(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:params) { attributes_for(:key).merge(user: user) } let(:params) { attributes_for(:key).merge(user: user) }
subject { described_class.new(admin, params) } subject { described_class.new(admin, params) }
......
...@@ -335,6 +335,7 @@ RSpec.describe Projects::CreateService, '#execute' do ...@@ -335,6 +335,7 @@ RSpec.describe Projects::CreateService, '#execute' do
context 'audit events' do context 'audit events' do
include_examples 'audit event logging' do include_examples 'audit event logging' do
let_it_be(:user) { create(:user) }
let(:operation) { create_project(user, opts) } let(:operation) { create_project(user, opts) }
let(:fail_condition!) do let(:fail_condition!) do
allow(Gitlab::VisibilityLevel).to receive(:allowed_for?).and_return(false) allow(Gitlab::VisibilityLevel).to receive(:allowed_for?).and_return(false)
......
...@@ -17,6 +17,8 @@ RSpec.describe Projects::GroupLinks::CreateService, '#execute' do ...@@ -17,6 +17,8 @@ RSpec.describe Projects::GroupLinks::CreateService, '#execute' do
context 'audit events' do context 'audit events' do
include_examples 'audit event logging' do include_examples 'audit event logging' do
let_it_be(:user) { create :user }
let_it_be(:project) { create :project }
let(:operation) { create_group_link(user, project, group, opts) } let(:operation) { create_group_link(user, project, group, opts) }
let(:fail_condition!) do let(:fail_condition!) do
create(:project_group_link, project: project, group: group) create(:project_group_link, project: project, group: group)
......
...@@ -123,6 +123,8 @@ RSpec.describe Projects::UpdateService, '#execute' do ...@@ -123,6 +123,8 @@ RSpec.describe Projects::UpdateService, '#execute' do
end end
context 'audit events' do context 'audit events' do
let_it_be(:user) { create(:user) }
let(:audit_event_params) do let(:audit_event_params) do
{ {
author_id: user.id, author_id: user.id,
......
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# Add user primary email to emails table if confirmed
class AddPrimaryEmailToEmailsIfUserConfirmed
INNER_BATCH_SIZE = 1_000
# Stubbed class to access the User table
class User < ActiveRecord::Base
include ::EachBatch
self.table_name = 'users'
self.inheritance_column = :_type_disabled
scope :confirmed, -> { where.not(confirmed_at: nil) }
has_many :emails
end
# Stubbed class to access the Emails table
class Email < ActiveRecord::Base
self.table_name = 'emails'
self.inheritance_column = :_type_disabled
belongs_to :user
end
def perform(start_id, end_id)
User.confirmed.where(id: start_id..end_id).select(:id, :email, :confirmed_at).each_batch(of: INNER_BATCH_SIZE) do |users|
current_time = Time.now.utc
attributes = users.map do |user|
{
user_id: user.id,
email: user.email,
confirmed_at: user.confirmed_at,
created_at: current_time,
updated_at: current_time
}
end
Email.insert_all(attributes)
end
mark_job_as_succeeded(start_id, end_id)
end
private
def mark_job_as_succeeded(*arguments)
Gitlab::Database::BackgroundMigrationJob.mark_all_as_succeeded(
'AddPrimaryEmailToEmailsIfUserConfirmed',
arguments
)
end
end
end
end
...@@ -69,6 +69,8 @@ module Gitlab ...@@ -69,6 +69,8 @@ module Gitlab
user_pb.emails << build_email(user.email, user.confirmed?) user_pb.emails << build_email(user.email, user.confirmed?)
user.emails.each do |email| user.emails.each do |email|
next if email.user_primary_email?
user_pb.emails << build_email(email.email, email.confirmed?) user_pb.emails << build_email(email.email, email.confirmed?)
end end
......
...@@ -3,8 +3,7 @@ ...@@ -3,8 +3,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Oauth::AuthorizationsController do RSpec.describe Oauth::AuthorizationsController do
let(:user) { create(:user, confirmed_at: confirmed_at) } let(:user) { create(:user) }
let(:confirmed_at) { 1.hour.ago }
let!(:application) { create(:oauth_application, scopes: 'api read_user', redirect_uri: 'http://example.com') } let!(:application) { create(:oauth_application, scopes: 'api read_user', redirect_uri: 'http://example.com') }
let(:params) do let(:params) do
{ {
...@@ -40,7 +39,7 @@ RSpec.describe Oauth::AuthorizationsController do ...@@ -40,7 +39,7 @@ RSpec.describe Oauth::AuthorizationsController do
end end
context 'when the user is unconfirmed' do context 'when the user is unconfirmed' do
let(:confirmed_at) { nil } let(:user) { create(:user, :unconfirmed) }
it 'returns 200 and renders error view' do it 'returns 200 and renders error view' do
subject subject
...@@ -73,8 +72,6 @@ RSpec.describe Oauth::AuthorizationsController do ...@@ -73,8 +72,6 @@ RSpec.describe Oauth::AuthorizationsController do
include_examples "Implicit grant can't be used in confidential application" include_examples "Implicit grant can't be used in confidential application"
context 'when the user is confirmed' do context 'when the user is confirmed' do
let(:confirmed_at) { 1.hour.ago }
context 'when there is already an access token for the application with a matching scope' do context 'when there is already an access token for the application with a matching scope' do
before do before do
scopes = Doorkeeper::OAuth::Scopes.from_string('api') scopes = Doorkeeper::OAuth::Scopes.from_string('api')
......
...@@ -4,6 +4,7 @@ require 'spec_helper' ...@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe 'Profile > Emails' do RSpec.describe 'Profile > Emails' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:other_user) { create(:user) }
before do before do
sign_in(user) sign_in(user)
...@@ -23,15 +24,25 @@ RSpec.describe 'Profile > Emails' do ...@@ -23,15 +24,25 @@ RSpec.describe 'Profile > Emails' do
expect(page).to have_content('Resend confirmation email') expect(page).to have_content('Resend confirmation email')
end end
it 'does not add a duplicate email' do it 'does not add an email that is the primary email of another user' do
fill_in('Email', with: user.email) fill_in('Email', with: other_user.email)
click_button('Add email address') click_button('Add email address')
email = user.emails.find_by(email: user.email) email = user.emails.find_by(email: other_user.email)
expect(email).to be_nil expect(email).to be_nil
expect(page).to have_content('Email has already been taken') expect(page).to have_content('Email has already been taken')
end end
it 'adds an email that is the primary email of the same user' do
fill_in('Email', with: user.email)
click_button('Add email address')
email = user.emails.find_by(email: user.email)
expect(email).to be_present
expect(page).to have_content("#{user.email} Verified")
expect(page).not_to have_content("#{user.email} Unverified")
end
it 'does not add an invalid email' do it 'does not add an invalid email' do
fill_in('Email', with: 'test.@example.com') fill_in('Email', with: 'test.@example.com')
click_button('Add email address') click_button('Add email address')
......
...@@ -4,6 +4,7 @@ require 'spec_helper' ...@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe 'User manages emails' do RSpec.describe 'User manages emails' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:other_user) { create(:user) }
before do before do
sign_in(user) sign_in(user)
...@@ -11,7 +12,7 @@ RSpec.describe 'User manages emails' do ...@@ -11,7 +12,7 @@ RSpec.describe 'User manages emails' do
visit(profile_emails_path) visit(profile_emails_path)
end end
it "shows user's emails" do it "shows user's emails", :aggregate_failures do
expect(page).to have_content(user.email) expect(page).to have_content(user.email)
user.emails.each do |email| user.emails.each do |email|
...@@ -19,7 +20,7 @@ RSpec.describe 'User manages emails' do ...@@ -19,7 +20,7 @@ RSpec.describe 'User manages emails' do
end end
end end
it 'adds an email' do it 'adds an email', :aggregate_failures do
fill_in('email_email', with: 'my@email.com') fill_in('email_email', with: 'my@email.com')
click_button('Add') click_button('Add')
...@@ -34,21 +35,21 @@ RSpec.describe 'User manages emails' do ...@@ -34,21 +35,21 @@ RSpec.describe 'User manages emails' do
end end
end end
it 'does not add a duplicate email' do it 'does not add an email that is the primary email of another user', :aggregate_failures do
fill_in('email_email', with: user.email) fill_in('email_email', with: other_user.email)
click_button('Add') click_button('Add')
email = user.emails.find_by(email: user.email) email = user.emails.find_by(email: other_user.email)
expect(email).to be_nil expect(email).to be_nil
expect(page).to have_content(user.email) expect(page).to have_content('Email has already been taken')
user.emails.each do |email| user.emails.each do |email|
expect(page).to have_content(email.email) expect(page).to have_content(email.email)
end end
end end
it 'removes an email' do it 'removes an email', :aggregate_failures do
fill_in('email_email', with: 'my@email.com') fill_in('email_email', with: 'my@email.com')
click_button('Add') click_button('Add')
......
...@@ -11,6 +11,7 @@ RSpec.describe 'GPG signed commits' do ...@@ -11,6 +11,7 @@ RSpec.describe 'GPG signed commits' do
perform_enqueued_jobs do perform_enqueued_jobs do
create :gpg_key, key: GpgHelpers::User1.public_key, user: user create :gpg_key, key: GpgHelpers::User1.public_key, user: user
user.reload # necessary to reload the association with gpg_keys
end end
visit project_commit_path(project, ref) visit project_commit_path(project, ref)
......
...@@ -383,7 +383,7 @@ RSpec.describe UsersHelper do ...@@ -383,7 +383,7 @@ RSpec.describe UsersHelper do
end end
context 'when `user.unconfirmed_email` is set' do context 'when `user.unconfirmed_email` is set' do
let(:user) { create(:user, unconfirmed_email: 'foo@bar.com') } let(:user) { create(:user, :unconfirmed, unconfirmed_email: 'foo@bar.com') }
it 'sets `modal_attributes.messageHtml` correctly' do it 'sets `modal_attributes.messageHtml` correctly' do
expect(Gitlab::Json.parse(confirm_user_data[:modal_attributes])['messageHtml']).to eq('This user has an unconfirmed email address (foo@bar.com). You may force a confirmation.') expect(Gitlab::Json.parse(confirm_user_data[:modal_attributes])['messageHtml']).to eq('This user has an unconfirmed email address (foo@bar.com). You may force a confirmation.')
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::AddPrimaryEmailToEmailsIfUserConfirmed do
let(:users) { table(:users) }
let(:emails) { table(:emails) }
let!(:unconfirmed_user) { users.create!(name: 'unconfirmed', email: 'unconfirmed@example.com', confirmed_at: nil, projects_limit: 100) }
let!(:confirmed_user_1) { users.create!(name: 'confirmed-1', email: 'confirmed-1@example.com', confirmed_at: 1.day.ago, projects_limit: 100) }
let!(:confirmed_user_2) { users.create!(name: 'confirmed-2', email: 'confirmed-2@example.com', confirmed_at: 1.day.ago, projects_limit: 100) }
let!(:email) { emails.create!(user_id: confirmed_user_1.id, email: 'confirmed-1@example.com', confirmed_at: 1.day.ago) }
let(:perform) { described_class.new.perform(users.first.id, users.last.id) }
it 'adds the primary email of confirmed users to Emails, unless already added', :aggregate_failures do
expect(emails.where(email: [unconfirmed_user.email, confirmed_user_2.email])).to be_empty
expect { perform }.not_to raise_error
expect(emails.where(email: unconfirmed_user.email).count).to eq(0)
expect(emails.where(email: confirmed_user_1.email, user_id: confirmed_user_1.id).count).to eq(1)
expect(emails.where(email: confirmed_user_2.email, user_id: confirmed_user_2.id).count).to eq(1)
email_2 = emails.find_by(email: confirmed_user_2.email, user_id: confirmed_user_2.id)
expect(email_2.confirmed_at).to eq(confirmed_user_2.reload.confirmed_at)
end
it 'sets timestamps on the created Emails' do
perform
email_2 = emails.find_by(email: confirmed_user_2.email, user_id: confirmed_user_2.id)
expect(email_2.created_at).not_to be_nil
expect(email_2.updated_at).not_to be_nil
end
context 'when a range of IDs is specified' do
let!(:confirmed_user_3) { users.create!(name: 'confirmed-3', email: 'confirmed-3@example.com', confirmed_at: 1.hour.ago, projects_limit: 100) }
let!(:confirmed_user_4) { users.create!(name: 'confirmed-4', email: 'confirmed-4@example.com', confirmed_at: 1.hour.ago, projects_limit: 100) }
it 'only acts on the specified range of IDs', :aggregate_failures do
expect do
described_class.new.perform(confirmed_user_2.id, confirmed_user_3.id)
end.to change { Email.count }.by(2)
expect(emails.where(email: confirmed_user_4.email).count).to eq(0)
end
end
end
...@@ -140,6 +140,8 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do ...@@ -140,6 +140,8 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
key: GpgHelpers::User1.public_key, key: GpgHelpers::User1.public_key,
user: user user: user
user.reload # necessary to reload the association with gpg_keys
expect(invalid_gpg_signature.reload.verification_status).to eq 'unverified_key' expect(invalid_gpg_signature.reload.verification_status).to eq 'unverified_key'
# InvalidGpgSignatureUpdater is called by the after_update hook # InvalidGpgSignatureUpdater is called by the after_update hook
......
...@@ -82,7 +82,7 @@ RSpec.describe Gitlab::Spamcheck::Client do ...@@ -82,7 +82,7 @@ RSpec.describe Gitlab::Spamcheck::Client do
end end
end end
describe '#build_user_proto_buf', :aggregate_failures do describe '#build_user_protobuf', :aggregate_failures do
it 'builds the expected protobuf object' do it 'builds the expected protobuf object' do
user_pb = described_class.new.send(:build_user_protobuf, user) user_pb = described_class.new.send(:build_user_protobuf, user)
expect(user_pb.username).to eq user.username expect(user_pb.username).to eq user.username
......
...@@ -12,7 +12,7 @@ RSpec.describe Gitlab::X509::Signature do ...@@ -12,7 +12,7 @@ RSpec.describe Gitlab::X509::Signature do
end end
shared_examples "a verified signature" do shared_examples "a verified signature" do
let_it_be(:user) { create(:user, email: X509Helpers::User1.certificate_email) } let!(:user) { create(:user, email: X509Helpers::User1.certificate_email) }
subject(:signature) do subject(:signature) do
described_class.new( described_class.new(
...@@ -30,10 +30,12 @@ RSpec.describe Gitlab::X509::Signature do ...@@ -30,10 +30,12 @@ RSpec.describe Gitlab::X509::Signature do
expect(signature.verification_status).to eq(:verified) expect(signature.verification_status).to eq(:verified)
end end
it "returns an unverified signature if the email matches but isn't confirmed" do context "if the email matches but isn't confirmed" do
user.update!(confirmed_at: nil) let!(:user) { create(:user, :unconfirmed, email: X509Helpers::User1.certificate_email) }
expect(signature.verification_status).to eq(:unverified) it "returns an unverified signature" do
expect(signature.verification_status).to eq(:unverified)
end
end end
it 'returns an unverified signature if email does not match' do it 'returns an unverified signature if email does not match' do
...@@ -297,7 +299,7 @@ RSpec.describe Gitlab::X509::Signature do ...@@ -297,7 +299,7 @@ RSpec.describe Gitlab::X509::Signature do
end end
context 'verified signature' do context 'verified signature' do
let_it_be(:user) { create(:user, email: X509Helpers::User1.certificate_email) } let_it_be(:user) { create(:user, :unconfirmed, email: X509Helpers::User1.certificate_email) }
subject(:signature) do subject(:signature) do
described_class.new( described_class.new(
...@@ -316,52 +318,56 @@ RSpec.describe Gitlab::X509::Signature do ...@@ -316,52 +318,56 @@ RSpec.describe Gitlab::X509::Signature do
allow(OpenSSL::X509::Store).to receive(:new).and_return(store) allow(OpenSSL::X509::Store).to receive(:new).and_return(store)
end end
it 'returns a verified signature if email does match' do context 'when user email is confirmed' do
expect(signature.x509_certificate).to have_attributes(certificate_attributes) before_all do
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes) user.confirm
expect(signature.verified_signature).to be_truthy end
expect(signature.verification_status).to eq(:verified)
end
it "returns an unverified signature if the email matches but isn't confirmed" do it 'returns a verified signature if email does match', :ggregate_failures do
user.update!(confirmed_at: nil) expect(signature.x509_certificate).to have_attributes(certificate_attributes)
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
expect(signature.verified_signature).to be_truthy
expect(signature.verification_status).to eq(:verified)
end
expect(signature.verification_status).to eq(:unverified) it 'returns an unverified signature if email does not match', :aggregate_failures do
end signature = described_class.new(
X509Helpers::User1.signed_tag_signature,
X509Helpers::User1.signed_tag_base_data,
"gitlab@example.com",
X509Helpers::User1.signed_commit_time
)
expect(signature.x509_certificate).to have_attributes(certificate_attributes)
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
expect(signature.verified_signature).to be_truthy
expect(signature.verification_status).to eq(:unverified)
end
it 'returns an unverified signature if email does not match' do it 'returns an unverified signature if email does match and time is wrong', :aggregate_failures do
signature = described_class.new( signature = described_class.new(
X509Helpers::User1.signed_tag_signature, X509Helpers::User1.signed_tag_signature,
X509Helpers::User1.signed_tag_base_data, X509Helpers::User1.signed_tag_base_data,
"gitlab@example.com", X509Helpers::User1.certificate_email,
X509Helpers::User1.signed_commit_time Time.new(2020, 2, 22)
) )
expect(signature.x509_certificate).to have_attributes(certificate_attributes)
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
expect(signature.verified_signature).to be_falsey
expect(signature.verification_status).to eq(:unverified)
end
expect(signature.x509_certificate).to have_attributes(certificate_attributes) it 'returns an unverified signature if certificate is revoked' do
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes) expect(signature.verification_status).to eq(:verified)
expect(signature.verified_signature).to be_truthy
expect(signature.verification_status).to eq(:unverified)
end
it 'returns an unverified signature if email does match and time is wrong' do signature.x509_certificate.revoked!
signature = described_class.new(
X509Helpers::User1.signed_tag_signature,
X509Helpers::User1.signed_tag_base_data,
X509Helpers::User1.certificate_email,
Time.new(2020, 2, 22)
)
expect(signature.x509_certificate).to have_attributes(certificate_attributes) expect(signature.verification_status).to eq(:unverified)
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes) end
expect(signature.verified_signature).to be_falsey
expect(signature.verification_status).to eq(:unverified)
end end
it 'returns an unverified signature if certificate is revoked' do it 'returns an unverified signature if the email matches but is not confirmed' do
expect(signature.verification_status).to eq(:verified)
signature.x509_certificate.revoked!
expect(signature.verification_status).to eq(:unverified) expect(signature.verification_status).to eq(:unverified)
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe ScheduleAddPrimaryEmailToEmailsIfUserConfirmed, :sidekiq do
let(:migration) { described_class.new }
let(:users) { table(:users) }
let!(:user_1) { users.create!(name: 'confirmed-user-1', email: 'confirmed-1@example.com', confirmed_at: 1.day.ago, projects_limit: 100) }
let!(:user_2) { users.create!(name: 'confirmed-user-2', email: 'confirmed-2@example.com', confirmed_at: 1.day.ago, projects_limit: 100) }
let!(:user_3) { users.create!(name: 'confirmed-user-3', email: 'confirmed-3@example.com', confirmed_at: 1.day.ago, projects_limit: 100) }
let!(:user_4) { users.create!(name: 'confirmed-user-4', email: 'confirmed-4@example.com', confirmed_at: 1.day.ago, projects_limit: 100) }
before do
stub_const("#{described_class.name}::BATCH_SIZE", 2)
stub_const("#{described_class.name}::INTERVAL", 2.minutes.to_i)
end
it 'schedules addition of primary email to emails in delayed batches' do
Sidekiq::Testing.fake! do
freeze_time do
migration.up
expect(described_class::MIGRATION).to be_scheduled_delayed_migration(2.minutes, user_1.id, user_2.id)
expect(described_class::MIGRATION).to be_scheduled_delayed_migration(4.minutes, user_3.id, user_4.id)
expect(BackgroundMigrationWorker.jobs.size).to eq(2)
end
end
end
end
...@@ -13,6 +13,15 @@ RSpec.describe Email do ...@@ -13,6 +13,15 @@ RSpec.describe Email do
it_behaves_like 'an object with RFC3696 compliant email-formatted attributes', :email do it_behaves_like 'an object with RFC3696 compliant email-formatted attributes', :email do
subject { build(:email) } subject { build(:email) }
end end
context 'when the email conflicts with the primary email of a different user' do
let(:user) { create(:user) }
let(:email) { build(:email, email: user.email) }
it 'is invalid' do
expect(email).to be_invalid
end
end
end end
it 'normalize email value' do it 'normalize email value' do
...@@ -33,7 +42,7 @@ RSpec.describe Email do ...@@ -33,7 +42,7 @@ RSpec.describe Email do
end end
describe 'scopes' do describe 'scopes' do
let(:user) { create(:user) } let(:user) { create(:user, :unconfirmed) }
it 'scopes confirmed emails' do it 'scopes confirmed emails' do
create(:email, :confirmed, user: user) create(:email, :confirmed, user: user)
......
...@@ -1123,7 +1123,7 @@ RSpec.describe User do ...@@ -1123,7 +1123,7 @@ RSpec.describe User do
end end
describe 'after commit hook' do describe 'after commit hook' do
describe '#update_emails_with_primary_email' do describe 'when the primary email is updated' do
before do before do
@user = create(:user, email: 'primary@example.com').tap do |user| @user = create(:user, email: 'primary@example.com').tap do |user|
user.skip_reconfirmation! user.skip_reconfirmation!
...@@ -1132,13 +1132,7 @@ RSpec.describe User do ...@@ -1132,13 +1132,7 @@ RSpec.describe User do
@user.reload @user.reload
end end
it 'gets called when email updated' do it 'keeps old primary to secondary emails when secondary is a new email' do
expect(@user).to receive(:update_emails_with_primary_email)
@user.update!(email: 'new_primary@example.com')
end
it 'adds old primary to secondary emails when secondary is a new email' do
@user.update!(email: 'new_primary@example.com') @user.update!(email: 'new_primary@example.com')
@user.reload @user.reload
...@@ -1146,22 +1140,6 @@ RSpec.describe User do ...@@ -1146,22 +1140,6 @@ RSpec.describe User do
expect(@user.emails.pluck(:email)).to match_array([@secondary.email, 'primary@example.com']) expect(@user.emails.pluck(:email)).to match_array([@secondary.email, 'primary@example.com'])
end end
it 'adds old primary to secondary emails if secondary is becoming a primary' do
@user.update!(email: @secondary.email)
@user.reload
expect(@user.emails.count).to eq 1
expect(@user.emails.first.email).to eq 'primary@example.com'
end
it 'transfers old confirmation values into new secondary' do
@user.update!(email: @secondary.email)
@user.reload
expect(@user.emails.count).to eq 1
expect(@user.emails.first.confirmed_at).not_to eq nil
end
context 'when the first email was unconfirmed and the second email gets confirmed' do context 'when the first email was unconfirmed and the second email gets confirmed' do
let(:user) { create(:user, :unconfirmed, email: 'should-be-unconfirmed@test.com') } let(:user) { create(:user, :unconfirmed, email: 'should-be-unconfirmed@test.com') }
...@@ -1178,11 +1156,8 @@ RSpec.describe User do ...@@ -1178,11 +1156,8 @@ RSpec.describe User do
expect(user).to be_confirmed expect(user).to be_confirmed
end end
it 'keeps the unconfirmed email unconfirmed' do it 'does not add unconfirmed email to secondary' do
email = user.emails.first expect(user.emails.map(&:email)).not_to include('should-be-unconfirmed@test.com')
expect(email.email).to eq('should-be-unconfirmed@test.com')
expect(email).not_to be_confirmed
end end
it 'has only one email association' do it 'has only one email association' do
...@@ -1244,7 +1219,7 @@ RSpec.describe User do ...@@ -1244,7 +1219,7 @@ RSpec.describe User do
expect(user.email).to eq(confirmed_email) expect(user.email).to eq(confirmed_email)
end end
it 'moves the old email' do it 'keeps the old email' do
email = user.reload.emails.first email = user.reload.emails.first
expect(email.email).to eq(old_confirmed_email) expect(email.email).to eq(old_confirmed_email)
...@@ -1499,7 +1474,7 @@ RSpec.describe User do ...@@ -1499,7 +1474,7 @@ RSpec.describe User do
allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(true) allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(true)
end end
let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: 'test@gitlab.com') } let(:user) { create(:user, :unconfirmed, unconfirmed_email: 'test@gitlab.com') }
it 'returns unconfirmed' do it 'returns unconfirmed' do
expect(user.confirmed?).to be_falsey expect(user.confirmed?).to be_falsey
...@@ -1509,6 +1484,22 @@ RSpec.describe User do ...@@ -1509,6 +1484,22 @@ RSpec.describe User do
user.confirm user.confirm
expect(user.confirmed?).to be_truthy expect(user.confirmed?).to be_truthy
end end
it 'adds the confirmed primary email to emails' do
expect(user.emails.confirmed.map(&:email)).not_to include(user.email)
user.confirm
expect(user.emails.confirmed.map(&:email)).to include(user.email)
end
end
context 'if the user is created with confirmed_at set to a time' do
let!(:user) { create(:user, email: 'test@gitlab.com', confirmed_at: Time.now.utc) }
it 'adds the confirmed primary email to emails upon creation' do
expect(user.emails.confirmed.map(&:email)).to include(user.email)
end
end end
describe '#to_reference' do describe '#to_reference' do
...@@ -2216,7 +2207,7 @@ RSpec.describe User do ...@@ -2216,7 +2207,7 @@ RSpec.describe User do
end end
context 'primary email not confirmed' do context 'primary email not confirmed' do
let(:user) { create(:user, confirmed_at: nil) } let(:user) { create(:user, :unconfirmed) }
let!(:email) { create(:email, :confirmed, user: user, email: 'foo@example.com') } let!(:email) { create(:email, :confirmed, user: user, email: 'foo@example.com') }
it 'finds user respecting the confirmed flag' do it 'finds user respecting the confirmed flag' do
...@@ -2231,7 +2222,7 @@ RSpec.describe User do ...@@ -2231,7 +2222,7 @@ RSpec.describe User do
end end
it 'returns nil when user is not confirmed' do it 'returns nil when user is not confirmed' do
user = create(:user, email: 'foo@example.com', confirmed_at: nil) user = create(:user, :unconfirmed, email: 'foo@example.com')
expect(described_class.find_by_any_email(user.email, confirmed: false)).to eq(user) expect(described_class.find_by_any_email(user.email, confirmed: false)).to eq(user)
expect(described_class.find_by_any_email(user.email, confirmed: true)).to be_nil expect(described_class.find_by_any_email(user.email, confirmed: true)).to be_nil
...@@ -6011,7 +6002,7 @@ RSpec.describe User do ...@@ -6011,7 +6002,7 @@ RSpec.describe User do
subject { user.confirmation_required_on_sign_in? } subject { user.confirmation_required_on_sign_in? }
context 'when user is confirmed' do context 'when user is confirmed' do
let(:user) { build_stubbed(:user) } let(:user) { create(:user) }
it 'is falsey' do it 'is falsey' do
expect(user.confirmed?).to be_truthy expect(user.confirmed?).to be_truthy
......
...@@ -1906,7 +1906,8 @@ RSpec.describe API::Users do ...@@ -1906,7 +1906,8 @@ RSpec.describe API::Users do
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers expect(response).to include_pagination_headers
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.first['email']).to eq(email.email) expect(json_response.first['email']).to eq(user.email)
expect(json_response.second['email']).to eq(email.email)
end end
it "returns a 404 for invalid ID" do it "returns a 404 for invalid ID" do
...@@ -2488,7 +2489,8 @@ RSpec.describe API::Users do ...@@ -2488,7 +2489,8 @@ RSpec.describe API::Users do
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers expect(response).to include_pagination_headers
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.first["email"]).to eq(email.email) expect(json_response.first['email']).to eq(user.email)
expect(json_response.second['email']).to eq(email.email)
end end
context "scopes" do context "scopes" do
......
...@@ -305,7 +305,7 @@ RSpec.describe UsersController do ...@@ -305,7 +305,7 @@ RSpec.describe UsersController do
context 'user with keys' do context 'user with keys' do
let!(:gpg_key) { create(:gpg_key, user: user) } let!(:gpg_key) { create(:gpg_key, user: user) }
let!(:another_gpg_key) { create(:another_gpg_key, user: user) } let!(:another_gpg_key) { create(:another_gpg_key, user: user.reload) }
shared_examples_for 'renders all verified GPG keys' do shared_examples_for 'renders all verified GPG keys' do
it 'renders all verified keys separated with a new line with text/plain content type' do it 'renders all verified keys separated with a new line with text/plain content type' do
......
...@@ -3,7 +3,8 @@ ...@@ -3,7 +3,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Emails::CreateService do RSpec.describe Emails::CreateService do
let(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:opts) { { email: 'new@email.com', user: user } } let(:opts) { { email: 'new@email.com', user: user } }
subject(:service) { described_class.new(user, opts) } subject(:service) { described_class.new(user, opts) }
...@@ -22,7 +23,7 @@ RSpec.describe Emails::CreateService do ...@@ -22,7 +23,7 @@ RSpec.describe Emails::CreateService do
it 'has the right user association' do it 'has the right user association' do
service.execute service.execute
expect(user.emails).to eq(Email.where(opts)) expect(user.emails).to include(Email.find_by(opts))
end end
end end
end end
...@@ -15,5 +15,15 @@ RSpec.describe Emails::DestroyService do ...@@ -15,5 +15,15 @@ RSpec.describe Emails::DestroyService do
expect(user.emails).not_to include(email) expect(user.emails).not_to include(email)
expect(response).to be true expect(response).to be true
end end
context 'when it corresponds to the user primary email' do
let(:email) { user.emails.find_by!(email: user.email) }
it 'does not remove the email and raises an exception' do
expect { service.execute(email) }.to raise_error(StandardError, 'Cannot delete primary email')
expect(user.emails).to include(email)
end
end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment