Commit 43510ac7 authored by James Lopez's avatar James Lopez

Merge branch '215919-email-validation' into 'master'

Change validation rules for profile email addresses

Closes #215919

See merge request gitlab-org/gitlab!30633
parents 88836247 96e9e1c2
...@@ -495,3 +495,6 @@ gem 'mail', '= 2.7.1' ...@@ -495,3 +495,6 @@ gem 'mail', '= 2.7.1'
# File encryption # File encryption
gem 'lockbox', '~> 0.3.3' gem 'lockbox', '~> 0.3.3'
# Email validation
gem 'valid_email', '~> 0.1'
...@@ -842,7 +842,7 @@ GEM ...@@ -842,7 +842,7 @@ GEM
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0) thor (>= 0.20.3, < 2.0)
rainbow (3.0.0) rainbow (3.0.0)
raindrops (0.19.0) raindrops (0.19.1)
rake (12.3.3) rake (12.3.3)
rb-fsevent (0.10.2) rb-fsevent (0.10.2)
rb-inotify (0.9.10) rb-inotify (0.9.10)
...@@ -1109,6 +1109,9 @@ GEM ...@@ -1109,6 +1109,9 @@ GEM
equalizer (~> 0.0.9) equalizer (~> 0.0.9)
parser (>= 2.6.5) parser (>= 2.6.5)
procto (~> 0.0.2) procto (~> 0.0.2)
valid_email (0.1.3)
activemodel
mail (>= 2.6.1)
validate_email (0.1.6) validate_email (0.1.6)
activemodel (>= 3.0) activemodel (>= 3.0)
mail (>= 2.2.5) mail (>= 2.2.5)
...@@ -1402,6 +1405,7 @@ DEPENDENCIES ...@@ -1402,6 +1405,7 @@ DEPENDENCIES
unicorn (~> 5.5) unicorn (~> 5.5)
unicorn-worker-killer (~> 0.4.4) unicorn-worker-killer (~> 0.4.4)
unleash (~> 0.1.5) unleash (~> 0.1.5)
valid_email (~> 0.1)
validates_hostname (~> 1.0.6) validates_hostname (~> 1.0.6)
version_sorter (~> 2.2.4) version_sorter (~> 2.2.4)
vmstat (~> 2.3.0) vmstat (~> 2.3.0)
......
...@@ -6,7 +6,8 @@ class Email < ApplicationRecord ...@@ -6,7 +6,8 @@ class Email < ApplicationRecord
belongs_to :user, optional: false belongs_to :user, optional: false
validates :email, presence: true, uniqueness: true, devise_email: true validates :email, presence: true, uniqueness: true
validate :validate_email_format
validate :unique_email, if: ->(email) { email.email_changed? } validate :unique_email, if: ->(email) { email.email_changed? }
scope :confirmed, -> { where.not(confirmed_at: nil) } scope :confirmed, -> { where.not(confirmed_at: nil) }
...@@ -30,6 +31,10 @@ class Email < ApplicationRecord ...@@ -30,6 +31,10 @@ class Email < ApplicationRecord
user.accept_pending_invitations! user.accept_pending_invitations!
end end
def validate_email_format
self.errors.add(:email, I18n.t(:invalid, scope: 'valid_email.validations.email')) unless ValidateEmail.valid?(self.email)
end
# once email is confirmed, update the gpg signatures # once email is confirmed, update the gpg signatures
def update_invalid_gpg_signatures def update_invalid_gpg_signatures
user.update_invalid_gpg_signatures if confirmed? user.update_invalid_gpg_signatures if confirmed?
......
---
title: Change validation rules for profile email addresses
merge_request: 30633
author:
type: fixed
...@@ -10,12 +10,20 @@ describe Profiles::EmailsController do ...@@ -10,12 +10,20 @@ describe Profiles::EmailsController do
end end
describe '#create' do describe '#create' do
let(:email_params) { { email: "add_email@example.com" } } context 'when email address is valid' do
let(:email_params) { { email: "add_email@example.com" } }
it 'sends an email confirmation' do it 'sends an email confirmation' do
expect { post(:create, params: { email: email_params }) }.to change { ActionMailer::Base.deliveries.size } expect { post(:create, params: { email: email_params }) }.to change { ActionMailer::Base.deliveries.size }
expect(ActionMailer::Base.deliveries.last.to).to eq [email_params[:email]] end
expect(ActionMailer::Base.deliveries.last.subject).to match "Confirmation instructions" end
context 'when email address is invalid' do
let(:email_params) { { email: "test.@example.com" } }
it 'does not send an email confirmation' do
expect { post(:create, params: { email: email_params }) }.not_to change { ActionMailer::Base.deliveries.size }
end
end end
end end
......
...@@ -31,6 +31,15 @@ describe 'Profile > Emails' do ...@@ -31,6 +31,15 @@ describe 'Profile > Emails' do
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 'does not add an invalid email' do
fill_in('Email', with: 'test.@example.com')
click_button('Add email address')
email = user.emails.find_by(email: email)
expect(email).to be_nil
expect(page).to have_content('Email is invalid')
end
end end
it 'User removes email' do it 'User removes email' do
......
...@@ -4,7 +4,7 @@ require 'spec_helper' ...@@ -4,7 +4,7 @@ require 'spec_helper'
describe Email do describe Email do
describe 'validations' do describe 'validations' do
it_behaves_like 'an object with email-formated attributes', :email do it_behaves_like 'an object with RFC3696 compliant email-formated attributes', :email do
subject { build(:email) } subject { build(:email) }
end end
end end
......
...@@ -296,7 +296,7 @@ describe User, :do_not_mock_admin_mode do ...@@ -296,7 +296,7 @@ describe User, :do_not_mock_admin_mode do
subject { build(:user) } subject { build(:user) }
end end
it_behaves_like 'an object with email-formated attributes', :public_email, :notification_email do it_behaves_like 'an object with RFC3696 compliant email-formated attributes', :public_email, :notification_email do
subject { build(:user).tap { |user| user.emails << build(:email, email: email_value) } } subject { build(:user).tap { |user| user.emails << build(:email, email: email_value) } }
end end
...@@ -916,7 +916,6 @@ describe User, :do_not_mock_admin_mode do ...@@ -916,7 +916,6 @@ describe User, :do_not_mock_admin_mode do
user.tap { |u| u.update!(email: new_email) }.reload user.tap { |u| u.update!(email: new_email) }.reload
end.to change(user, :unconfirmed_email).to(new_email) end.to change(user, :unconfirmed_email).to(new_email)
end end
it 'does not change :notification_email' do it 'does not change :notification_email' do
expect do expect do
user.tap { |u| u.update!(email: new_email) }.reload user.tap { |u| u.update!(email: new_email) }.reload
......
...@@ -44,3 +44,44 @@ RSpec.shared_examples 'an object with email-formated attributes' do |*attributes ...@@ -44,3 +44,44 @@ RSpec.shared_examples 'an object with email-formated attributes' do |*attributes
end end
end end
end end
RSpec.shared_examples 'an object with RFC3696 compliant email-formated attributes' do |*attributes|
attributes.each do |attribute|
describe "specifically its :#{attribute} attribute" do
%w[
info@example.com
info+test@example.com
o'reilly@example.com
].each do |valid_email|
context "with a value of '#{valid_email}'" do
let(:email_value) { valid_email }
it 'is valid' do
subject.send("#{attribute}=", valid_email)
expect(subject).to be_valid
end
end
end
%w[
foobar
test@test@example.com
test.test.@example.com
.test.test@example.com
mailto:test@example.com
lol!'+=?><#$%^&*()@gmail.com
].each do |invalid_email|
context "with a value of '#{invalid_email}'" do
let(:email_value) { invalid_email }
it 'is invalid' do
subject.send("#{attribute}=", invalid_email)
expect(subject).to be_invalid
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