Commit 1fe7a42a authored by Sean McGivern's avatar Sean McGivern

Merge branch 'digitalmoksha/gitlab-ce-feature/verify_secondary_emails' into 'master'

Send a confirmation email when the user adds a secondary email address

Closes #37385, #28621, and #36959

See merge request gitlab-org/gitlab-ce!14703
parents 3594a67d 782c017c
...@@ -392,11 +392,11 @@ table.u2f-registrations { ...@@ -392,11 +392,11 @@ table.u2f-registrations {
} }
} }
.gpg-email-badge { .email-badge {
display: inline; display: inline;
margin-right: $gl-padding / 2; margin-right: $gl-padding / 2;
.gpg-email-badge-email { .email-badge-email {
display: inline; display: inline;
margin-right: $gl-padding / 4; margin-right: $gl-padding / 4;
} }
......
...@@ -155,7 +155,7 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -155,7 +155,7 @@ class Admin::UsersController < Admin::ApplicationController
def remove_email def remove_email
email = user.emails.find(params[:email_id]) email = user.emails.find(params[:email_id])
success = Emails::DestroyService.new(current_user, user: user, email: email.email).execute success = Emails::DestroyService.new(current_user, user: user).execute(email)
respond_to do |format| respond_to do |format|
if success if success
......
...@@ -10,13 +10,14 @@ class ConfirmationsController < Devise::ConfirmationsController ...@@ -10,13 +10,14 @@ class ConfirmationsController < Devise::ConfirmationsController
users_almost_there_path users_almost_there_path
end end
def after_confirmation_path_for(resource_name, resource) def after_confirmation_path_for(_resource_name, resource)
if signed_in?(resource_name) # incoming resource can either be a :user or an :email
if signed_in?(:user)
after_sign_in(resource) after_sign_in(resource)
else else
Gitlab::AppLogger.info("Email Confirmed: username=#{resource.username} email=#{resource.email} ip=#{request.remote_ip}") Gitlab::AppLogger.info("Email Confirmed: username=#{resource.username} email=#{resource.email} ip=#{request.remote_ip}")
flash[:notice] += " Please sign in." flash[:notice] += " Please sign in."
new_session_path(resource_name) new_session_path(:user)
end end
end end
......
class Profiles::EmailsController < Profiles::ApplicationController class Profiles::EmailsController < Profiles::ApplicationController
before_action :find_email, only: [:destroy, :resend_confirmation_instructions]
def index def index
@primary = current_user.email @primary_email = current_user.email
@emails = current_user.emails.order_id_desc @emails = current_user.emails.order_id_desc
end end
def create def create
@email = Emails::CreateService.new(current_user, email_params.merge(user: current_user)).execute @email = Emails::CreateService.new(current_user, email_params.merge(user: current_user)).execute
unless @email.errors.blank?
if @email.errors.blank?
NotificationService.new.new_email(@email)
else
flash[:alert] = @email.errors.full_messages.first flash[:alert] = @email.errors.full_messages.first
end end
...@@ -17,9 +16,7 @@ class Profiles::EmailsController < Profiles::ApplicationController ...@@ -17,9 +16,7 @@ class Profiles::EmailsController < Profiles::ApplicationController
end end
def destroy def destroy
@email = current_user.emails.find(params[:id]) Emails::DestroyService.new(current_user, user: current_user).execute(@email)
Emails::DestroyService.new(current_user, user: current_user, email: @email.email).execute
respond_to do |format| respond_to do |format|
format.html { redirect_to profile_emails_url, status: 302 } format.html { redirect_to profile_emails_url, status: 302 }
...@@ -27,9 +24,23 @@ class Profiles::EmailsController < Profiles::ApplicationController ...@@ -27,9 +24,23 @@ class Profiles::EmailsController < Profiles::ApplicationController
end end
end end
def resend_confirmation_instructions
if Emails::ConfirmService.new(current_user, user: current_user).execute(@email)
flash[:notice] = "Confirmation email sent to #{@email.email}"
else
flash[:alert] = "There was a problem sending the confirmation email"
end
redirect_to profile_emails_url
end
private private
def email_params def email_params
params.require(:email).permit(:email) params.require(:email).permit(:email)
end end
def find_email
@email = current_user.emails.find(params[:id])
end
end end
...@@ -7,12 +7,6 @@ module Emails ...@@ -7,12 +7,6 @@ module Emails
mail(to: @user.notification_email, subject: subject("Account was created for you")) mail(to: @user.notification_email, subject: subject("Account was created for you"))
end end
def new_email_email(email_id)
@email = Email.find(email_id)
@current_user = @user = @email.user
mail(to: @user.notification_email, subject: subject("Email was added to your account"))
end
def new_ssh_key_email(key_id) def new_ssh_key_email(key_id)
@key = Key.find_by(id: key_id) @key = Key.find_by(id: key_id)
......
...@@ -7,6 +7,13 @@ class Email < ActiveRecord::Base ...@@ -7,6 +7,13 @@ class Email < ActiveRecord::Base
validates :email, presence: true, uniqueness: true, email: true validates :email, presence: true, uniqueness: true, email: true
validate :unique_email, if: ->(email) { email.email_changed? } validate :unique_email, if: ->(email) { email.email_changed? }
scope :confirmed, -> { where.not(confirmed_at: nil) }
after_commit :update_invalid_gpg_signatures, if: -> { previous_changes.key?('confirmed_at') }
devise :confirmable
self.reconfirmable = false # currently email can't be changed, no need to reconfirm
def email=(value) def email=(value)
write_attribute(:email, value.downcase.strip) write_attribute(:email, value.downcase.strip)
end end
...@@ -14,4 +21,9 @@ class Email < ActiveRecord::Base ...@@ -14,4 +21,9 @@ class Email < ActiveRecord::Base
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 User.exists?(email: self.email)
end end
# once email is confirmed, update the gpg signatures
def update_invalid_gpg_signatures
user.update_invalid_gpg_signatures if confirmed?
end
end end
...@@ -163,15 +163,16 @@ class User < ActiveRecord::Base ...@@ -163,15 +163,16 @@ class User < ActiveRecord::Base
before_validation :sanitize_attrs before_validation :sanitize_attrs
before_validation :set_notification_email, if: :email_changed? before_validation :set_notification_email, if: :email_changed?
before_validation :set_public_email, if: :public_email_changed? before_validation :set_public_email, if: :public_email_changed?
after_update :update_emails_with_primary_email, if: :email_changed?
before_save :ensure_authentication_token, :ensure_incoming_email_token before_save :ensure_authentication_token, :ensure_incoming_email_token
before_save :ensure_user_rights_and_limits, if: :external_changed? before_save :ensure_user_rights_and_limits, if: :external_changed?
before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) } before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) }
before_save :check_for_verified_email, if: ->(user) { user.email_changed? && !user.new_record? }
after_save :ensure_namespace_correct after_save :ensure_namespace_correct
after_destroy :post_destroy_hook
after_commit :update_emails_with_primary_email, on: :update, if: -> { previous_changes.key?('email') }
after_commit :update_invalid_gpg_signatures, on: :update, if: -> { previous_changes.key?('email') } after_commit :update_invalid_gpg_signatures, on: :update, if: -> { previous_changes.key?('email') }
after_initialize :set_projects_limit after_initialize :set_projects_limit
after_destroy :post_destroy_hook
# User's Layout preference # User's Layout preference
enum layout: [:fixed, :fluid] enum layout: [:fixed, :fluid]
...@@ -525,12 +526,24 @@ class User < ActiveRecord::Base ...@@ -525,12 +526,24 @@ class User < ActiveRecord::Base
errors.add(:public_email, "is not an email you own") unless all_emails.include?(public_email) errors.add(:public_email, "is not an email you own") unless all_emails.include?(public_email)
end end
# see if the new email is already a verified secondary email
def check_for_verified_email
skip_reconfirmation! if emails.confirmed.where(email: self.email).any?
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
def update_emails_with_primary_email def update_emails_with_primary_email
previous_email = previous_changes[:email][0] # grab this before the DestroyService is called
primary_email_record = emails.find_by(email: email) primary_email_record = emails.find_by(email: email)
if primary_email_record Emails::DestroyService.new(self, user: self).execute(primary_email_record) if primary_email_record
Emails::DestroyService.new(self, user: self, email: email).execute
Emails::CreateService.new(self, user: self, email: email_was).execute # the original primary email was confirmed, and we want that to carry over. We don't
end # 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: confirmed_at)
end end
def update_invalid_gpg_signatures def update_invalid_gpg_signatures
...@@ -816,6 +829,10 @@ class User < ActiveRecord::Base ...@@ -816,6 +829,10 @@ class User < ActiveRecord::Base
avatar_path(args) || GravatarService.new.execute(email, size, scale, username: username) avatar_path(args) || GravatarService.new.execute(email, size, scale, username: username)
end end
def primary_email_verified?
confirmed? && !temp_oauth_email?
end
def all_emails def all_emails
all_emails = [] all_emails = []
all_emails << email unless temp_oauth_email? all_emails << email unless temp_oauth_email?
...@@ -823,6 +840,18 @@ class User < ActiveRecord::Base ...@@ -823,6 +840,18 @@ class User < ActiveRecord::Base
all_emails all_emails
end end
def verified_emails
verified_emails = []
verified_emails << email if primary_email_verified?
verified_emails.concat(emails.confirmed.pluck(:email))
verified_emails
end
def verified_email?(check_email)
downcased = check_email.downcase
email == downcased ? primary_email_verified? : emails.confirmed.where(email: downcased).exists?
end
def hook_attrs def hook_attrs
{ {
name: name, name: name,
...@@ -1047,10 +1076,6 @@ class User < ActiveRecord::Base ...@@ -1047,10 +1076,6 @@ class User < ActiveRecord::Base
ensure_rss_token! ensure_rss_token!
end end
def verified_email?(email)
self.email == email
end
def sync_attribute?(attribute) def sync_attribute?(attribute)
return true if ldap_user? && attribute == :email return true if ldap_user? && attribute == :email
......
module Emails module Emails
class BaseService class BaseService
def initialize(current_user, opts) def initialize(current_user, params = {})
@current_user = current_user @current_user, @params = current_user, params.dup
@user = opts.delete(:user) @user = params.delete(:user)
@email = opts[:email]
end end
end end
end end
module Emails
class ConfirmService < ::Emails::BaseService
def execute(email)
email.resend_confirmation_instructions
end
end
end
module Emails module Emails
class CreateService < ::Emails::BaseService class CreateService < ::Emails::BaseService
def execute def execute(extra_params = {})
@user.emails.create(email: @email) @user.emails.create(@params.merge(extra_params))
end end
end end
end end
module Emails module Emails
class DestroyService < ::Emails::BaseService class DestroyService < ::Emails::BaseService
def execute def execute(email)
update_secondary_emails! if Email.find_by_email!(@email).destroy email.destroy && update_secondary_emails!
end end
private private
......
...@@ -31,13 +31,6 @@ class NotificationService ...@@ -31,13 +31,6 @@ class NotificationService
end end
end end
# Always notify user about email added to profile
def new_email(email)
if email.user&.can?(:receive_notifications)
mailer.new_email_email(email.id).deliver_later
end
end
# When create an issue we should send an email to: # When create an issue we should send an email to:
# #
# * issue assignee if their notification level is not Disabled # * issue assignee if their notification level is not Disabled
......
- confirmation_link = confirmation_url(@resource, confirmation_token: @token)
- if @resource.unconfirmed_email.present?
#content
= email_default_heading(@resource.unconfirmed_email)
%p Click the link below to confirm your email address.
#cta
= link_to 'Confirm your email address', confirmation_link
- else
#content
- if Gitlab.com?
= email_default_heading('Thanks for signing up to GitLab!')
- else
= email_default_heading("Welcome, #{@resource.name}!")
%p To get started, click the link below to confirm your account.
#cta
= link_to 'Confirm your account', confirmation_link
<% if @resource.unconfirmed_email.present? %>
<%= @resource.unconfirmed_email %>,
Use the link below to confirm your email address.
<% else %>
<% if Gitlab.com? %>
Thanks for signing up to GitLab!
<% else %>
Welcome, <%= @resource.name %>!
<% end %>
To get started, use the link below to confirm your account.
<% end %>
<%= confirmation_url(@resource, confirmation_token: @token) %>
#content
= email_default_heading("#{@resource.user.name}, you've added an additional email!")
%p Click the link below to confirm your email address (#{@resource.email})
#cta
= link_to 'Confirm your email address', confirmation_url(@resource, confirmation_token: @token)
%p
If this email was added in error, you can remove it here:
= link_to "Emails", profile_emails_url
Hi <%= @user.name %>! <%= @resource.user.name %>, you've added an additional email!
A new email was added to your account: Use the link below to confirm your email address (<%= @resource.email %>)
email.................. <%= @email.email %> <%= confirmation_url(@resource, confirmation_token: @token) %>
If this email was added in error, you can remove it here: <%= profile_emails_url %> If this email was added in error, you can remove it here: <%= profile_emails_url %>
- confirmation_link = confirmation_url(@resource, confirmation_token: @token) = render partial: "confirmation_instructions_#{@resource.is_a?(User) ? 'account' : 'secondary'}"
- if @resource.unconfirmed_email.present?
#content
= email_default_heading(@resource.unconfirmed_email)
%p Click the link below to confirm your email address.
#cta
= link_to confirmation_link, confirmation_link
- else
#content
- if Gitlab.com?
= email_default_heading('Thanks for signing up to GitLab!')
- else
= email_default_heading("Welcome, #{@resource.name}!")
%p To get started, click the link below to confirm your account.
#cta
= link_to confirmation_link, confirmation_link
Welcome, <%= @resource.name %>! <%= render partial: "confirmation_instructions_#{@resource.is_a?(User) ? 'account' : 'secondary'}" %>
\ No newline at end of file
<% if @resource.unconfirmed_email.present? %>
You can confirm your email (<%= @resource.unconfirmed_email %>) through the link below:
<% else %>
You can confirm your account through the link below:
<% end %>
<%= confirmation_url(@resource, confirmation_token: @token) %>
%p
Hi #{@user.name}!
%p
A new email was added to your account:
%p
email:
%code= @email.email
%p
If this email was added in error, you can remove it here:
= link_to "Emails", profile_emails_url
...@@ -32,19 +32,25 @@ ...@@ -32,19 +32,25 @@
All email addresses will be used to identify your commits. All email addresses will be used to identify your commits.
%ul.well-list %ul.well-list
%li %li
= @primary = render partial: 'shared/email_with_badge', locals: { email: @primary_email, verified: current_user.confirmed? }
%span.pull-right %span.pull-right
%span.label.label-success Primary email %span.label.label-success Primary email
- if @primary === current_user.public_email - if @primary_email === current_user.public_email
%span.label.label-info Public email %span.label.label-info Public email
- if @primary === current_user.notification_email - if @primary_email === current_user.notification_email
%span.label.label-info Notification email %span.label.label-info Notification email
- @emails.each do |email| - @emails.each do |email|
%li %li
= email.email = render partial: 'shared/email_with_badge', locals: { email: email.email, verified: email.confirmed? }
%span.pull-right %span.pull-right
- if email.email === current_user.public_email - if email.email === current_user.public_email
%span.label.label-info Public email %span.label.label-info Public email
- if email.email === current_user.notification_email - if email.email === current_user.notification_email
%span.label.label-info Notification email %span.label.label-info Notification email
= link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-warning prepend-left-10' - unless email.confirmed?
- confirm_title = "#{email.confirmation_sent_at ? 'Resend' : 'Send'} confirmation email"
= link_to confirm_title, resend_confirmation_instructions_profile_email_path(email), method: :put, class: 'btn btn-sm btn-warning prepend-left-10'
= link_to profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-danger prepend-left-10' do
%span.sr-only Remove
= icon('trash')
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
= icon 'key', class: "settings-list-icon hidden-xs" = icon 'key', class: "settings-list-icon hidden-xs"
.key-list-item-info .key-list-item-info
- key.emails_with_verified_status.map do |email, verified| - key.emails_with_verified_status.map do |email, verified|
= render partial: 'email_with_badge', locals: { email: email, verified: verified } = render partial: 'shared/email_with_badge', locals: { email: email, verified: verified }
.description .description
%code= key.fingerprint %code= key.fingerprint
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- css_classes << (verified ? 'verified': 'unverified') - css_classes << (verified ? 'verified': 'unverified')
- text = verified ? 'Verified' : 'Unverified' - text = verified ? 'Verified' : 'Unverified'
.gpg-email-badge .email-badge
.gpg-email-badge-email= email .email-badge-email= email
%div{ class: css_classes } %div{ class: css_classes }
= text = text
---
title: A confirmation email is now sent when adding a secondary email address
merge_request:
author: digitalmoksha
type: added
...@@ -175,7 +175,7 @@ Devise.setup do |config| ...@@ -175,7 +175,7 @@ Devise.setup do |config|
# Configure the default scope given to Warden. By default it's the first # Configure the default scope given to Warden. By default it's the first
# devise role declared in your routes (usually :user). # devise role declared in your routes (usually :user).
# config.default_scope = :user config.default_scope = :user # now have an :email scope as well, so set the default
# Configure sign_out behavior. # Configure sign_out behavior.
# Sign_out action can be scoped (i.e. /users/sign_out affects only :user scope). # Sign_out action can be scoped (i.e. /users/sign_out affects only :user scope).
......
# for secondary email confirmations - uses the same confirmation controller as :users
devise_for :emails, path: 'profile/emails', controllers: { confirmations: :confirmations }
resource :profile, only: [:show, :update] do resource :profile, only: [:show, :update] do
member do member do
get :audit_log get :audit_log
...@@ -28,7 +31,11 @@ resource :profile, only: [:show, :update] do ...@@ -28,7 +31,11 @@ resource :profile, only: [:show, :update] do
put :revoke put :revoke
end end
end end
resources :emails, only: [:index, :create, :destroy] resources :emails, only: [:index, :create, :destroy] do
member do
put :resend_confirmation_instructions
end
end
resources :chat_names, only: [:index, :new, :create, :destroy] do resources :chat_names, only: [:index, :new, :create, :destroy] do
collection do collection do
delete :deny delete :deny
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddEmailConfirmation < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
# When a migration requires downtime you **must** uncomment the following
# constant and define a short and easy to understand explanation as to why the
# migration requires downtime.
# DOWNTIME_REASON = ''
# When using the methods "add_concurrent_index", "remove_concurrent_index" or
# "add_column_with_default" you must disable the use of transactions
# as these methods can not run in an existing transaction.
# When using "add_concurrent_index" or "remove_concurrent_index" methods make sure
# that either of them is the _only_ method called in the migration,
# any other changes should go in a separate migration.
# This ensures that upon failure _only_ the index creation or removing fails
# and can be retried or reverted easily.
#
# To disable transactions uncomment the following line and remove these
# comments:
# disable_ddl_transaction!
def change
add_column :emails, :confirmation_token, :string
add_column :emails, :confirmed_at, :datetime_with_timezone
add_column :emails, :confirmation_sent_at, :datetime_with_timezone
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddEmailConfirmationIndex < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
# When a migration requires downtime you **must** uncomment the following
# constant and define a short and easy to understand explanation as to why the
# migration requires downtime.
# DOWNTIME_REASON = ''
# When using the methods "add_concurrent_index", "remove_concurrent_index" or
# "add_column_with_default" you must disable the use of transactions
# as these methods can not run in an existing transaction.
# When using "add_concurrent_index" or "remove_concurrent_index" methods make sure
# that either of them is the _only_ method called in the migration,
# any other changes should go in a separate migration.
# This ensures that upon failure _only_ the index creation or removing fails
# and can be retried or reverted easily.
#
# To disable transactions uncomment the following line and remove these
# comments:
disable_ddl_transaction!
# Not necessary to remove duplicates, as :confirmation_token is a new column
def up
add_concurrent_index :emails, :confirmation_token, unique: true
end
def down
remove_concurrent_index :emails, :confirmation_token if index_exists?(:emails, :confirmation_token)
end
end
...@@ -514,8 +514,12 @@ ActiveRecord::Schema.define(version: 20171004121444) do ...@@ -514,8 +514,12 @@ ActiveRecord::Schema.define(version: 20171004121444) do
t.string "email", null: false t.string "email", null: false
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
end end
add_index "emails", ["confirmation_token"], name: "index_emails_on_confirmation_token", unique: true, using: :btree
add_index "emails", ["email"], name: "index_emails_on_email", unique: true, using: :btree add_index "emails", ["email"], name: "index_emails_on_email", unique: true, using: :btree
add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree
......
...@@ -26,7 +26,7 @@ to be uploaded to GitLab. For a signature to be verified three conditions need ...@@ -26,7 +26,7 @@ to be uploaded to GitLab. For a signature to be verified three conditions need
to be met: to be met:
1. The public key needs to be added your GitLab account 1. The public key needs to be added your GitLab account
1. One of the emails in the GPG key matches your **primary** email 1. One of the emails in the GPG key matches a **verified** email address you use in GitLab
1. The committer's email matches the verified email from the gpg key 1. The committer's email matches the verified email from the gpg key
## Generating a GPG key ## Generating a GPG key
...@@ -94,7 +94,7 @@ started: ...@@ -94,7 +94,7 @@ started:
``` ```
1. Enter you real name, the email address to be associated with this key (should 1. Enter you real name, the email address to be associated with this key (should
match the primary email address you use in GitLab) and an optional comment match a verified email address you use in GitLab) and an optional comment
(press <kbd>Enter</kbd> to skip): (press <kbd>Enter</kbd> to skip):
``` ```
......
...@@ -331,7 +331,6 @@ module API ...@@ -331,7 +331,6 @@ module API
email = Emails::CreateService.new(current_user, declared_params(include_missing: false).merge(user: user)).execute email = Emails::CreateService.new(current_user, declared_params(include_missing: false).merge(user: user)).execute
if email.errors.blank? if email.errors.blank?
NotificationService.new.new_email(email)
present email, with: Entities::Email present email, with: Entities::Email
else else
render_validation_error!(email) render_validation_error!(email)
...@@ -369,10 +368,8 @@ module API ...@@ -369,10 +368,8 @@ module API
not_found!('Email') unless email not_found!('Email') unless email
destroy_conditionally!(email) do |email| destroy_conditionally!(email) do |email|
Emails::DestroyService.new(current_user, user: user, email: email.email).execute Emails::DestroyService.new(current_user, user: user).execute(email)
end end
user.update_secondary_emails!
end end
desc 'Delete a user. Available only for admins.' do desc 'Delete a user. Available only for admins.' do
...@@ -677,7 +674,6 @@ module API ...@@ -677,7 +674,6 @@ module API
email = Emails::CreateService.new(current_user, declared_params.merge(user: current_user)).execute email = Emails::CreateService.new(current_user, declared_params.merge(user: current_user)).execute
if email.errors.blank? if email.errors.blank?
NotificationService.new.new_email(email)
present email, with: Entities::Email present email, with: Entities::Email
else else
render_validation_error!(email) render_validation_error!(email)
...@@ -693,10 +689,8 @@ module API ...@@ -693,10 +689,8 @@ module API
not_found!('Email') unless email not_found!('Email') unless email
destroy_conditionally!(email) do |email| destroy_conditionally!(email) do |email|
Emails::DestroyService.new(current_user, user: current_user, email: email.email).execute Emails::DestroyService.new(current_user, user: current_user).execute(email)
end end
current_user.update_secondary_emails!
end end
desc 'Get a list of user activities' desc 'Get a list of user activities'
......
require 'spec_helper'
describe Profiles::EmailsController do
let(:user) { create(:user) }
before do
sign_in(user)
end
describe '#create' do
let(:email_params) { { email: "add_email@example.com" } }
it 'sends an email confirmation' do
expect { post(:create, { email: email_params }) }.to change { ActionMailer::Base.deliveries.size }
expect(ActionMailer::Base.deliveries.last.to).to eq [email_params[:email]]
expect(ActionMailer::Base.deliveries.last.subject).to match "Confirmation instructions"
end
end
describe '#resend_confirmation_instructions' do
let(:email_params) { { email: "add_email@example.com" } }
it 'resends an email confirmation' do
email = user.emails.create(email: 'add_email@example.com')
expect { put(:resend_confirmation_instructions, { id: email }) }.to change { ActionMailer::Base.deliveries.size }
expect(ActionMailer::Base.deliveries.last.to).to eq [email_params[:email]]
expect(ActionMailer::Base.deliveries.last.subject).to match "Confirmation instructions"
end
it 'unable to resend an email confirmation' do
expect { put(:resend_confirmation_instructions, { id: 1 }) }.not_to change { ActionMailer::Base.deliveries.size }
end
end
end
...@@ -15,6 +15,20 @@ describe ProfilesController do ...@@ -15,6 +15,20 @@ describe ProfilesController do
expect(user.unconfirmed_email).to eq('john@gmail.com') expect(user.unconfirmed_email).to eq('john@gmail.com')
end end
it "allows an email update without confirmation if existing verified email" do
user = create(:user)
create(:email, :confirmed, user: user, email: 'john@gmail.com')
sign_in(user)
put :update,
user: { email: "john@gmail.com", name: "John" }
user.reload
expect(response.status).to eq(302)
expect(user.unconfirmed_email).to eq nil
end
it "ignores an email update from a user with an external email address" do it "ignores an email update from a user with an external email address" do
stub_omniauth_setting(sync_profile_from_provider: ['ldap']) stub_omniauth_setting(sync_profile_from_provider: ['ldap'])
stub_omniauth_setting(sync_profile_attributes: true) stub_omniauth_setting(sync_profile_attributes: true)
......
...@@ -2,5 +2,7 @@ FactoryGirl.define do ...@@ -2,5 +2,7 @@ FactoryGirl.define do
factory :email do factory :email do
user user
email { generate(:email_alias) } email { generate(:email_alias) }
trait(:confirmed) { confirmed_at Time.now }
end end
end end
require 'rails_helper'
feature 'Profile > Emails' do
let(:user) { create(:user) }
before do
sign_in(user)
end
describe 'User adds an email' do
before do
visit profile_emails_path
end
scenario 'saves the new email' do
fill_in('Email', with: 'my@email.com')
click_button('Add email address')
expect(page).to have_content('my@email.com Unverified')
expect(page).to have_content("#{user.email} Verified")
expect(page).to have_content('Resend confirmation email')
end
scenario 'does not add a duplicate email' do
fill_in('Email', with: user.email)
click_button('Add email address')
email = user.emails.find_by(email: user.email)
expect(email).to be_nil
expect(page).to have_content('Email has already been taken')
end
end
scenario 'User removes email' do
user.emails.create(email: 'my@email.com')
visit profile_emails_path
expect(page).to have_content("my@email.com")
click_link('Remove')
expect(page).not_to have_content("my@email.com")
end
scenario 'User confirms email' do
email = user.emails.create(email: 'my@email.com')
visit profile_emails_path
expect(page).to have_content("#{email.email} Unverified")
email.confirm
expect(email.confirmed?).to be_truthy
visit profile_emails_path
expect(page).to have_content("#{email.email} Verified")
end
scenario 'User re-sends confirmation email' do
email = user.emails.create(email: 'my@email.com')
visit profile_emails_path
expect { click_link("Resend confirmation email") }.to change { ActionMailer::Base.deliveries.size }
expect(page).to have_content("Confirmation email sent to #{email.email}")
end
scenario 'old unconfirmed emails show Send Confirmation button' do
email = user.emails.create(email: 'my@email.com')
email.update_attribute(:confirmation_sent_at, nil)
visit profile_emails_path
expect(page).not_to have_content('Resend confirmation email')
expect(page).to have_content('Send confirmation email')
end
end
...@@ -4,7 +4,7 @@ feature 'Profile > GPG Keys' do ...@@ -4,7 +4,7 @@ feature 'Profile > GPG Keys' do
let(:user) { create(:user, email: GpgHelpers::User2.emails.first) } let(:user) { create(:user, email: GpgHelpers::User2.emails.first) }
before do before do
login_as(user) sign_in(user)
end end
describe 'User adds a key' do describe 'User adds a key' do
......
...@@ -120,29 +120,4 @@ describe Emails::Profile do ...@@ -120,29 +120,4 @@ describe Emails::Profile do
it { expect { Notify.new_gpg_key_email('foo') }.not_to raise_error } it { expect { Notify.new_gpg_key_email('foo') }.not_to raise_error }
end end
end end
describe 'user added email' do
let(:email) { create(:email) }
subject { Notify.new_email_email(email.id) }
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'is sent to the new user' do
is_expected.to deliver_to email.user.email
end
it 'has the correct subject' do
is_expected.to have_subject /^Email was added to your account$/i
end
it 'contains the new email address' do
is_expected.to have_body_text /#{email.email}/
end
it 'includes a link to emails page' do
is_expected.to have_body_text /#{profile_emails_path}/
end
end
end end
...@@ -11,4 +11,33 @@ describe Email do ...@@ -11,4 +11,33 @@ describe Email do
expect(described_class.new(email: ' inFO@exAMPLe.com ').email) expect(described_class.new(email: ' inFO@exAMPLe.com ').email)
.to eq 'info@example.com' .to eq 'info@example.com'
end end
describe '#update_invalid_gpg_signatures' do
let(:user) do
create(:user, email: 'tula.torphy@abshire.ca').tap do |user|
user.skip_reconfirmation!
end
end
let(:user) { create(:user) }
it 'synchronizes the gpg keys when the email is updated' do
email = user.emails.create(email: 'new@email.com')
expect(user).to receive(:update_invalid_gpg_signatures)
email.confirm
end
end
describe 'scopes' do
let(:user) { create(:user) }
it 'scopes confirmed emails' do
create(:email, :confirmed, user: user)
create(:email, user: user)
expect(user.emails.count).to eq 2
expect(user.emails.confirmed.count).to eq 1
end
end
end end
...@@ -90,11 +90,20 @@ describe GpgKey do ...@@ -90,11 +90,20 @@ describe GpgKey do
it 'email is verified if the user has the matching email' do it 'email is verified if the user has the matching email' do
user = create :user, email: 'bette.cartwright@example.com' user = create :user, email: 'bette.cartwright@example.com'
gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key, user: user gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key, user: user
create :email, user: user
user.reload
expect(gpg_key.emails_with_verified_status).to eq( expect(gpg_key.emails_with_verified_status).to eq(
'bette.cartwright@example.com' => true, 'bette.cartwright@example.com' => true,
'bette.cartwright@example.net' => false 'bette.cartwright@example.net' => false
) )
create :email, :confirmed, user: user, email: 'bette.cartwright@example.net'
user.reload
expect(gpg_key.emails_with_verified_status).to eq(
'bette.cartwright@example.com' => true,
'bette.cartwright@example.net' => true
)
end end
end end
......
...@@ -360,9 +360,22 @@ describe User do ...@@ -360,9 +360,22 @@ describe User do
expect(external_user.projects_limit).to be 0 expect(external_user.projects_limit).to be 0
end end
end end
describe '#check_for_verified_email' do
let(:user) { create(:user) }
let(:secondary) { create(:email, :confirmed, email: 'secondary@example.com', user: user) }
it 'allows a verfied secondary email to be used as the primary without needing reconfirmation' do
user.update_attributes!(email: secondary.email)
user.reload
expect(user.email).to eq secondary.email
expect(user.unconfirmed_email).to eq nil
expect(user.confirmed?).to be_truthy
end
end
end end
describe 'after update hook' do describe 'after commit hook' do
describe '.update_invalid_gpg_signatures' do describe '.update_invalid_gpg_signatures' do
let(:user) do let(:user) do
create(:user, email: 'tula.torphy@abshire.ca').tap do |user| create(:user, email: 'tula.torphy@abshire.ca').tap do |user|
...@@ -376,10 +389,50 @@ describe User do ...@@ -376,10 +389,50 @@ describe User do
end end
it 'synchronizes the gpg keys when the email is updated' do it 'synchronizes the gpg keys when the email is updated' do
expect(user).to receive(:update_invalid_gpg_signatures) expect(user).to receive(:update_invalid_gpg_signatures).at_most(:twice)
user.update_attributes!(email: 'shawnee.ritchie@denesik.com') user.update_attributes!(email: 'shawnee.ritchie@denesik.com')
end end
end end
describe '#update_emails_with_primary_email' do
before do
@user = create(:user, email: 'primary@example.com').tap do |user|
user.skip_reconfirmation!
end
@secondary = create :email, email: 'secondary@example.com', user: @user
@user.reload
end
it 'gets called when email updated' do
expect(@user).to receive(:update_emails_with_primary_email)
@user.update_attributes!(email: 'new_primary@example.com')
end
it 'adds old primary to secondary emails when secondary is a new email ' do
@user.update_attributes!(email: 'new_primary@example.com')
@user.reload
expect(@user.emails.count).to eq 2
expect(@user.emails.pluck(:email)).to match_array([@secondary.email, 'primary@example.com'])
end
it 'adds old primary to secondary emails if secondary is becoming a primary' do
@user.update_attributes!(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_attributes!(email: @secondary.email)
@user.reload
expect(@user.emails.count).to eq 1
expect(@user.emails.first.confirmed_at).not_to eq nil
end
end
end end
describe '#update_tracked_fields!', :clean_gitlab_redis_shared_state do describe '#update_tracked_fields!', :clean_gitlab_redis_shared_state do
...@@ -467,6 +520,7 @@ describe User do ...@@ -467,6 +520,7 @@ describe User do
describe '#generate_password' do describe '#generate_password' do
it "does not generate password by default" do it "does not generate password by default" do
user = create(:user, password: 'abcdefghe') user = create(:user, password: 'abcdefghe')
expect(user.password).to eq('abcdefghe') expect(user.password).to eq('abcdefghe')
end end
end end
...@@ -474,6 +528,7 @@ describe User do ...@@ -474,6 +528,7 @@ describe User do
describe 'authentication token' do describe 'authentication token' do
it "has authentication token" do it "has authentication token" do
user = create(:user) user = create(:user)
expect(user.authentication_token).not_to be_blank expect(user.authentication_token).not_to be_blank
end end
end end
...@@ -481,6 +536,7 @@ describe User do ...@@ -481,6 +536,7 @@ describe User do
describe 'ensure incoming email token' do describe 'ensure incoming email token' do
it 'has incoming email token' do it 'has incoming email token' do
user = create(:user) user = create(:user)
expect(user.incoming_email_token).not_to be_blank expect(user.incoming_email_token).not_to be_blank
end end
end end
...@@ -523,6 +579,7 @@ describe User do ...@@ -523,6 +579,7 @@ describe User do
it 'ensures an rss token on read' do it 'ensures an rss token on read' do
user = create(:user, rss_token: nil) user = create(:user, rss_token: nil)
rss_token = user.rss_token rss_token = user.rss_token
expect(rss_token).not_to be_blank expect(rss_token).not_to be_blank
expect(user.reload.rss_token).to eq rss_token expect(user.reload.rss_token).to eq rss_token
end end
...@@ -633,6 +690,7 @@ describe User do ...@@ -633,6 +690,7 @@ describe User do
it "blocks user" do it "blocks user" do
user.block user.block
expect(user.blocked?).to be_truthy expect(user.blocked?).to be_truthy
end end
end end
...@@ -966,6 +1024,7 @@ describe User do ...@@ -966,6 +1024,7 @@ describe User do
it 'is case-insensitive' do it 'is case-insensitive' do
user = create(:user, username: 'JohnDoe') user = create(:user, username: 'JohnDoe')
expect(described_class.find_by_username('JOHNDOE')).to eq user expect(described_class.find_by_username('JOHNDOE')).to eq user
end end
end end
...@@ -978,6 +1037,7 @@ describe User do ...@@ -978,6 +1037,7 @@ describe User do
it 'is case-insensitive' do it 'is case-insensitive' do
user = create(:user, username: 'JohnDoe') user = create(:user, username: 'JohnDoe')
expect(described_class.find_by_username!('JOHNDOE')).to eq user expect(described_class.find_by_username!('JOHNDOE')).to eq user
end end
end end
...@@ -1067,11 +1127,13 @@ describe User do ...@@ -1067,11 +1127,13 @@ describe User do
it 'is true if avatar is image' do it 'is true if avatar is image' do
user.update_attribute(:avatar, 'uploads/avatar.png') user.update_attribute(:avatar, 'uploads/avatar.png')
expect(user.avatar_type).to be_truthy expect(user.avatar_type).to be_truthy
end end
it 'is false if avatar is html page' do it 'is false if avatar is html page' do
user.update_attribute(:avatar, 'uploads/avatar.html') user.update_attribute(:avatar, 'uploads/avatar.html')
expect(user.avatar_type).to eq(['only images allowed']) expect(user.avatar_type).to eq(['only images allowed'])
end end
end end
...@@ -1094,6 +1156,50 @@ describe User do ...@@ -1094,6 +1156,50 @@ describe User do
end end
end end
describe '#all_emails' do
let(:user) { create(:user) }
it 'returns all emails' do
email_confirmed = create :email, user: user, confirmed_at: Time.now
email_unconfirmed = create :email, user: user
user.reload
expect(user.all_emails).to match_array([user.email, email_unconfirmed.email, email_confirmed.email])
end
end
describe '#verified_emails' do
let(:user) { create(:user) }
it 'returns only confirmed emails' do
email_confirmed = create :email, user: user, confirmed_at: Time.now
create :email, user: user
user.reload
expect(user.verified_emails).to match_array([user.email, email_confirmed.email])
end
end
describe '#verified_email?' do
let(:user) { create(:user) }
it 'returns true when the email is verified/confirmed' do
email_confirmed = create :email, user: user, confirmed_at: Time.now
create :email, user: user
user.reload
expect(user.verified_email?(user.email)).to be_truthy
expect(user.verified_email?(email_confirmed.email.titlecase)).to be_truthy
end
it 'returns false when the email is not verified/confirmed' do
email_unconfirmed = create :email, user: user
user.reload
expect(user.verified_email?(email_unconfirmed.email)).to be_falsy
end
end
describe '#requires_ldap_check?' do describe '#requires_ldap_check?' do
let(:user) { described_class.new } let(:user) { described_class.new }
...@@ -1101,6 +1207,7 @@ describe User do ...@@ -1101,6 +1207,7 @@ describe User do
# Create a condition which would otherwise cause 'true' to be returned # Create a condition which would otherwise cause 'true' to be returned
allow(user).to receive(:ldap_user?).and_return(true) allow(user).to receive(:ldap_user?).and_return(true)
user.last_credential_check_at = nil user.last_credential_check_at = nil
expect(user.requires_ldap_check?).to be_falsey expect(user.requires_ldap_check?).to be_falsey
end end
...@@ -1111,6 +1218,7 @@ describe User do ...@@ -1111,6 +1218,7 @@ describe User do
it 'is false for non-LDAP users' do it 'is false for non-LDAP users' do
allow(user).to receive(:ldap_user?).and_return(false) allow(user).to receive(:ldap_user?).and_return(false)
expect(user.requires_ldap_check?).to be_falsey expect(user.requires_ldap_check?).to be_falsey
end end
...@@ -1121,11 +1229,13 @@ describe User do ...@@ -1121,11 +1229,13 @@ describe User do
it 'is true when the user has never had an LDAP check before' do it 'is true when the user has never had an LDAP check before' do
user.last_credential_check_at = nil user.last_credential_check_at = nil
expect(user.requires_ldap_check?).to be_truthy expect(user.requires_ldap_check?).to be_truthy
end end
it 'is true when the last LDAP check happened over 1 hour ago' do it 'is true when the last LDAP check happened over 1 hour ago' do
user.last_credential_check_at = 2.hours.ago user.last_credential_check_at = 2.hours.ago
expect(user.requires_ldap_check?).to be_truthy expect(user.requires_ldap_check?).to be_truthy
end end
end end
...@@ -1136,16 +1246,19 @@ describe User do ...@@ -1136,16 +1246,19 @@ describe User do
describe '#ldap_user?' do describe '#ldap_user?' do
it 'is true if provider name starts with ldap' do it 'is true if provider name starts with ldap' do
user = create(:omniauth_user, provider: 'ldapmain') user = create(:omniauth_user, provider: 'ldapmain')
expect(user.ldap_user?).to be_truthy expect(user.ldap_user?).to be_truthy
end end
it 'is false for other providers' do it 'is false for other providers' do
user = create(:omniauth_user, provider: 'other-provider') user = create(:omniauth_user, provider: 'other-provider')
expect(user.ldap_user?).to be_falsey expect(user.ldap_user?).to be_falsey
end end
it 'is false if no extern_uid is provided' do it 'is false if no extern_uid is provided' do
user = create(:omniauth_user, extern_uid: nil) user = create(:omniauth_user, extern_uid: nil)
expect(user.ldap_user?).to be_falsey expect(user.ldap_user?).to be_falsey
end end
end end
...@@ -1153,6 +1266,7 @@ describe User do ...@@ -1153,6 +1266,7 @@ describe User do
describe '#ldap_identity' do describe '#ldap_identity' do
it 'returns ldap identity' do it 'returns ldap identity' do
user = create :omniauth_user user = create :omniauth_user
expect(user.ldap_identity.provider).not_to be_empty expect(user.ldap_identity.provider).not_to be_empty
end end
end end
...@@ -1162,6 +1276,7 @@ describe User do ...@@ -1162,6 +1276,7 @@ describe User do
it 'blocks user flaging the action caming from ldap' do it 'blocks user flaging the action caming from ldap' do
user.ldap_block user.ldap_block
expect(user.blocked?).to be_truthy expect(user.blocked?).to be_truthy
expect(user.ldap_blocked?).to be_truthy expect(user.ldap_blocked?).to be_truthy
end end
...@@ -1234,18 +1349,22 @@ describe User do ...@@ -1234,18 +1349,22 @@ describe User do
expect(user.starred?(project2)).to be_falsey expect(user.starred?(project2)).to be_falsey
star1 = UsersStarProject.create!(project: project1, user: user) star1 = UsersStarProject.create!(project: project1, user: user)
expect(user.starred?(project1)).to be_truthy expect(user.starred?(project1)).to be_truthy
expect(user.starred?(project2)).to be_falsey expect(user.starred?(project2)).to be_falsey
star2 = UsersStarProject.create!(project: project2, user: user) star2 = UsersStarProject.create!(project: project2, user: user)
expect(user.starred?(project1)).to be_truthy expect(user.starred?(project1)).to be_truthy
expect(user.starred?(project2)).to be_truthy expect(user.starred?(project2)).to be_truthy
star1.destroy star1.destroy
expect(user.starred?(project1)).to be_falsey expect(user.starred?(project1)).to be_falsey
expect(user.starred?(project2)).to be_truthy expect(user.starred?(project2)).to be_truthy
star2.destroy star2.destroy
expect(user.starred?(project1)).to be_falsey expect(user.starred?(project1)).to be_falsey
expect(user.starred?(project2)).to be_falsey expect(user.starred?(project2)).to be_falsey
end end
...@@ -1257,9 +1376,13 @@ describe User do ...@@ -1257,9 +1376,13 @@ describe User do
project = create(:project, :public) project = create(:project, :public)
expect(user.starred?(project)).to be_falsey expect(user.starred?(project)).to be_falsey
user.toggle_star(project) user.toggle_star(project)
expect(user.starred?(project)).to be_truthy expect(user.starred?(project)).to be_truthy
user.toggle_star(project) user.toggle_star(project)
expect(user.starred?(project)).to be_falsey expect(user.starred?(project)).to be_falsey
end end
end end
...@@ -1438,9 +1561,11 @@ describe User do ...@@ -1438,9 +1561,11 @@ describe User do
user = create(:user) user = create(:user)
member = group.add_developer(user) member = group.add_developer(user)
expect(user.authorized_projects).to include(project) expect(user.authorized_projects).to include(project)
member.destroy member.destroy
expect(user.authorized_projects).not_to include(project) expect(user.authorized_projects).not_to include(project)
end end
...@@ -1461,9 +1586,11 @@ describe User do ...@@ -1461,9 +1586,11 @@ describe User do
project = create(:project, :private, namespace: user1.namespace) project = create(:project, :private, namespace: user1.namespace)
project.team << [user2, Gitlab::Access::DEVELOPER] project.team << [user2, Gitlab::Access::DEVELOPER]
expect(user2.authorized_projects).to include(project) expect(user2.authorized_projects).to include(project)
project.destroy project.destroy
expect(user2.authorized_projects).not_to include(project) expect(user2.authorized_projects).not_to include(project)
end end
...@@ -1473,9 +1600,11 @@ describe User do ...@@ -1473,9 +1600,11 @@ describe User do
user = create(:user) user = create(:user)
group.add_developer(user) group.add_developer(user)
expect(user.authorized_projects).to include(project) expect(user.authorized_projects).to include(project)
group.destroy group.destroy
expect(user.authorized_projects).not_to include(project) expect(user.authorized_projects).not_to include(project)
end end
end end
...@@ -2019,7 +2148,9 @@ describe User do ...@@ -2019,7 +2148,9 @@ describe User do
it 'creates the namespace' do it 'creates the namespace' do
expect(user.namespace).to be_nil expect(user.namespace).to be_nil
user.save! user.save!
expect(user.namespace).not_to be_nil expect(user.namespace).not_to be_nil
end end
end end
...@@ -2040,11 +2171,13 @@ describe User do ...@@ -2040,11 +2171,13 @@ describe User do
it 'updates the namespace name' do it 'updates the namespace name' do
user.update_attributes!(username: new_username) user.update_attributes!(username: new_username)
expect(user.namespace.name).to eq(new_username) expect(user.namespace.name).to eq(new_username)
end end
it 'updates the namespace path' do it 'updates the namespace path' do
user.update_attributes!(username: new_username) user.update_attributes!(username: new_username)
expect(user.namespace.path).to eq(new_username) expect(user.namespace.path).to eq(new_username)
end end
...@@ -2058,6 +2191,7 @@ describe User do ...@@ -2058,6 +2191,7 @@ describe User do
it 'adds the namespace errors to the user' do it 'adds the namespace errors to the user' do
user.update_attributes(username: new_username) user.update_attributes(username: new_username)
expect(user.errors.full_messages.first).to eq('Namespace name has already been taken') expect(user.errors.full_messages.first).to eq('Namespace name has already been taken')
end end
end end
...@@ -2074,56 +2208,49 @@ describe User do ...@@ -2074,56 +2208,49 @@ describe User do
end end
end end
describe '#verified_email?' do
it 'returns true when the email is the primary email' do
user = build :user, email: 'email@example.com'
expect(user.verified_email?('email@example.com')).to be true
end
it 'returns false when the email is not the primary email' do
user = build :user, email: 'email@example.com'
expect(user.verified_email?('other_email@example.com')).to be false
end
end
describe '#sync_attribute?' do describe '#sync_attribute?' do
let(:user) { described_class.new } let(:user) { described_class.new }
context 'oauth user' do context 'oauth user' do
it 'returns true if name can be synced' do it 'returns true if name can be synced' do
stub_omniauth_setting(sync_profile_attributes: %w(name location)) stub_omniauth_setting(sync_profile_attributes: %w(name location))
expect(user.sync_attribute?(:name)).to be_truthy expect(user.sync_attribute?(:name)).to be_truthy
end end
it 'returns true if email can be synced' do it 'returns true if email can be synced' do
stub_omniauth_setting(sync_profile_attributes: %w(name email)) stub_omniauth_setting(sync_profile_attributes: %w(name email))
expect(user.sync_attribute?(:email)).to be_truthy expect(user.sync_attribute?(:email)).to be_truthy
end end
it 'returns true if location can be synced' do it 'returns true if location can be synced' do
stub_omniauth_setting(sync_profile_attributes: %w(location email)) stub_omniauth_setting(sync_profile_attributes: %w(location email))
expect(user.sync_attribute?(:email)).to be_truthy expect(user.sync_attribute?(:email)).to be_truthy
end end
it 'returns false if name can not be synced' do it 'returns false if name can not be synced' do
stub_omniauth_setting(sync_profile_attributes: %w(location email)) stub_omniauth_setting(sync_profile_attributes: %w(location email))
expect(user.sync_attribute?(:name)).to be_falsey expect(user.sync_attribute?(:name)).to be_falsey
end end
it 'returns false if email can not be synced' do it 'returns false if email can not be synced' do
stub_omniauth_setting(sync_profile_attributes: %w(location email)) stub_omniauth_setting(sync_profile_attributes: %w(location email))
expect(user.sync_attribute?(:name)).to be_falsey expect(user.sync_attribute?(:name)).to be_falsey
end end
it 'returns false if location can not be synced' do it 'returns false if location can not be synced' do
stub_omniauth_setting(sync_profile_attributes: %w(location email)) stub_omniauth_setting(sync_profile_attributes: %w(location email))
expect(user.sync_attribute?(:name)).to be_falsey expect(user.sync_attribute?(:name)).to be_falsey
end end
it 'returns true for all syncable attributes if all syncable attributes can be synced' do it 'returns true for all syncable attributes if all syncable attributes can be synced' do
stub_omniauth_setting(sync_profile_attributes: true) stub_omniauth_setting(sync_profile_attributes: true)
expect(user.sync_attribute?(:name)).to be_truthy expect(user.sync_attribute?(:name)).to be_truthy
expect(user.sync_attribute?(:email)).to be_truthy expect(user.sync_attribute?(:email)).to be_truthy
expect(user.sync_attribute?(:location)).to be_truthy expect(user.sync_attribute?(:location)).to be_truthy
...@@ -2139,6 +2266,7 @@ describe User do ...@@ -2139,6 +2266,7 @@ describe User do
context 'ldap user' do context 'ldap user' do
it 'returns true for email if ldap user' do it 'returns true for email if ldap user' do
allow(user).to receive(:ldap_user?).and_return(true) allow(user).to receive(:ldap_user?).and_return(true)
expect(user.sync_attribute?(:name)).to be_falsey expect(user.sync_attribute?(:name)).to be_falsey
expect(user.sync_attribute?(:email)).to be_truthy expect(user.sync_attribute?(:email)).to be_truthy
expect(user.sync_attribute?(:location)).to be_falsey expect(user.sync_attribute?(:location)).to be_falsey
...@@ -2147,6 +2275,7 @@ describe User do ...@@ -2147,6 +2275,7 @@ describe User do
it 'returns true for email and location if ldap user and location declared as syncable' do it 'returns true for email and location if ldap user and location declared as syncable' do
allow(user).to receive(:ldap_user?).and_return(true) allow(user).to receive(:ldap_user?).and_return(true)
stub_omniauth_setting(sync_profile_attributes: %w(location)) stub_omniauth_setting(sync_profile_attributes: %w(location))
expect(user.sync_attribute?(:name)).to be_falsey expect(user.sync_attribute?(:name)).to be_falsey
expect(user.sync_attribute?(:email)).to be_truthy expect(user.sync_attribute?(:email)).to be_truthy
expect(user.sync_attribute?(:location)).to be_truthy expect(user.sync_attribute?(:location)).to be_truthy
......
require 'spec_helper'
describe Emails::ConfirmService do
let(:user) { create(:user) }
subject(:service) { described_class.new(user) }
describe '#execute' do
it 'sends a confirmation email again' do
email = user.emails.create(email: 'new@email.com')
mail = service.execute(email)
expect(mail.subject).to eq('Confirmation instructions')
end
end
end
...@@ -12,6 +12,11 @@ describe Emails::CreateService do ...@@ -12,6 +12,11 @@ describe Emails::CreateService do
expect(Email.where(opts)).not_to be_empty expect(Email.where(opts)).not_to be_empty
end end
it 'creates an email with additional attributes' do
expect { service.execute(confirmation_token: 'abc') }.to change { Email.count }.by(1)
expect(Email.where(opts).first.confirmation_token).to eq 'abc'
end
it 'has the right user association' do it 'has the right user association' do
service.execute service.execute
......
...@@ -4,11 +4,11 @@ describe Emails::DestroyService do ...@@ -4,11 +4,11 @@ describe Emails::DestroyService do
let!(:user) { create(:user) } let!(:user) { create(:user) }
let!(:email) { create(:email, user: user) } let!(:email) { create(:email, user: user) }
subject(:service) { described_class.new(user, user: user, email: email.email) } subject(:service) { described_class.new(user, user: user) }
describe '#execute' do describe '#execute' do
it 'removes an email' do it 'removes an email' do
expect { service.execute }.to change { user.emails.count }.by(-1) expect { service.execute(email) }.to change { user.emails.count }.by(-1)
end end
end end
end end
...@@ -105,18 +105,6 @@ describe NotificationService, :mailer do ...@@ -105,18 +105,6 @@ describe NotificationService, :mailer do
end end
end end
describe 'Email' do
describe '#new_email' do
let!(:email) { create(:email) }
it { expect(notification.new_email(email)).to be_truthy }
it 'sends email to email owner' do
expect { notification.new_email(email) }.to change { ActionMailer::Base.deliveries.size }.by(1)
end
end
end
describe 'Notes' do describe 'Notes' do
context 'issue note' do context 'issue note' do
let(:project) { create(:project, :private) } let(:project) { create(:project, :private) }
......
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