Browse Source

Fixes #4410 - Automatic third-party account linking notification.

Dusan Vuckovic 2 years ago
parent
commit
c8075ae8df

+ 30 - 1
app/models/authorization.rb

@@ -2,7 +2,7 @@
 
 class Authorization < ApplicationModel
   belongs_to    :user, optional: true
-  after_create  :delete_user_cache
+  after_create  :delete_user_cache, :notification_send
   after_update  :delete_user_cache
   after_destroy :delete_user_cache
   validates     :user_id,  presence: true
@@ -107,4 +107,33 @@ class Authorization < ApplicationModel
     user.touch # rubocop:disable Rails/SkipsModelValidations
   end
 
+  # An account is considered linked if the user originates from a source other than the current authorization provider.
+  def linked_account?
+    user.source != provider
+  end
+
+  def notification_send
+
+    # Send a notification only if the feature is turned on and the account is linked.
+    return if !Setting.get('auth_third_party_linking_notification') || !user || !linked_account?
+
+    template = 'user_auth_provider'
+
+    if user.email.blank?
+      Rails.logger.info { "Unable to send a notification (#{template}) to user_id: #{user.id} be cause of missing email address." }
+      return
+    end
+
+    Rails.logger.debug { "Send notification (#{template}) to: #{user.email}" }
+
+    NotificationFactory::Mailer.notification(
+      template: template,
+      user:     user,
+      objects:  {
+        user:     user,
+        provider: provider,
+      }
+    )
+  end
+
 end

+ 9 - 0
app/views/mailer/user_auth_provider/en.html.erb

@@ -0,0 +1,9 @@
+#{config.product_name} account linked with a third-party application
+
+<div>Hi #{user.firstname},</div>
+<br>
+<div>It looks like you linked your account <b>with a third-party application</b> "#{provider}".</div>
+<br>
+<div>If this wasn't you, please remove the linked account from your profile and change your third-party application password. Somebody might have gained unauthorized access to your account on the third-party service.</div>
+<br>
+<div>Your #{config.product_name} Team</div>

+ 35 - 0
db/migrate/20221209095600_issue_4410_account_linking_notification.rb

@@ -0,0 +1,35 @@
+# Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
+
+class Issue4410AccountLinkingNotification < ActiveRecord::Migration[6.1]
+  def change
+    # return if it's a new setup
+    return if !Setting.exists?(name: 'system_init_done')
+
+    Setting.create_if_not_exists(
+      title:       'Automatic account linking notification',
+      name:        'auth_third_party_linking_notification',
+      area:        'Security::ThirdPartyAuthentication',
+      description: 'Enables sending of an email notification to a user when they link their account with a third-party application.',
+      options:     {
+        form: [
+          {
+            display: '',
+            null:    true,
+            name:    'auth_third_party_linking_notification',
+            tag:     'boolean',
+            options: {
+              true  => 'yes',
+              false => 'no',
+            },
+          },
+        ],
+      },
+      preferences: {
+        permission: ['admin.security'],
+        prio:       20,
+      },
+      state:       false,
+      frontend:    false
+    )
+  end
+end

+ 26 - 0
db/seeds/settings.rb

@@ -1268,6 +1268,32 @@ Setting.create_if_not_exists(
   state:       false,
   frontend:    false
 )
+Setting.create_if_not_exists(
+  title:       __('Automatic account linking notification'),
+  name:        'auth_third_party_linking_notification',
+  area:        'Security::ThirdPartyAuthentication',
+  description: __('Enables sending of an email notification to a user when they link their account with a third-party application.'),
+  options:     {
+    form: [
+      {
+        display: '',
+        null:    true,
+        name:    'auth_third_party_linking_notification',
+        tag:     'boolean',
+        options: {
+          true  => 'yes',
+          false => 'no',
+        },
+      },
+    ],
+  },
+  preferences: {
+    permission: ['admin.security'],
+    prio:       20,
+  },
+  state:       false,
+  frontend:    false
+)
 Setting.create_if_not_exists(
   title:       __('Authentication via %s'),
   name:        'auth_twitter',

+ 14 - 0
i18n/zammad.pot

@@ -80,6 +80,12 @@ msgstr ""
 msgid "# #{ticket.title}\n_<#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}|Ticket##{ticket.number}>: Will escalate at #{ticket.escalation_at}_\nThe ticket (#{ticket.title}) from \"#{ticket.customer.longname}\" will escalate at \"#{ticket.escalation_at}\"!\n\n<% if @objects[:article] %>\n#{article.body_as_text}\n<% end %>\n"
 msgstr ""
 
+#. This is the template file app/views/mailer/user_auth_provider/en.html.erb in ERB/HTML format.
+#. Please make sure to translate it to a valid corresponding output structure.
+#: app/views/mailer/user_auth_provider/en.html.erb
+msgid "#{config.product_name} account linked with a third-party application\n\n<div>Hi #{user.firstname},</div>\n<br>\n<div>It looks like you linked your account <b>with a third-party application</b> \"#{provider}\".</div>\n<br>\n<div>If this wasn't you, please remove the linked account from your profile and change your third-party application password. Somebody might have gained unauthorized access to your account on the third-party service.</div>\n<br>\n<div>Your #{config.product_name} Team</div>\n"
+msgstr ""
+
 #. This is the template file app/views/mailer/user_device_new_location/en.html.erb in ERB/HTML format.
 #. Please make sure to translate it to a valid corresponding output structure.
 #: app/views/mailer/user_device_new_location/en.html.erb
@@ -1406,6 +1412,10 @@ msgstr ""
 msgid "Automatic account link on initial logon"
 msgstr ""
 
+#: db/seeds/settings.rb
+msgid "Automatic account linking notification"
+msgstr ""
+
 #: app/assets/javascripts/app/controllers/_integration/clearbit.coffee
 msgid "Automatically enrich your customers and organizations with fresh, up-to-date intel. Map data directly to object fields."
 msgstr ""
@@ -4146,6 +4156,10 @@ msgstr ""
 msgid "Enables priority icons in ticket overviews."
 msgstr ""
 
+#: db/seeds/settings.rb
+msgid "Enables sending of an email notification to a user when they link their account with a third-party application."
+msgstr ""
+
 #: db/seeds/settings.rb
 msgid "Enables the automatic linking of an existing account on initial login via a third party application. If this is disabled, an existing user must first log into Zammad and then link his \"Third Party\" account to his Zammad account via Profile -> Linked Accounts."
 msgstr ""

+ 15 - 0
spec/db/migrate/issue_4410_account_linking_notification_spec.rb

@@ -0,0 +1,15 @@
+# Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
+
+require 'rails_helper'
+
+RSpec.describe Issue4410AccountLinkingNotification, type: :db_migration do
+  before do
+    Setting.find_by(name: 'auth_third_party_linking_notification').destroy!
+
+    migrate
+  end
+
+  it 'does create auth_third_party_linking_notification setting' do
+    expect(Setting.exists?(name: 'auth_third_party_linking_notification')).to be(true)
+  end
+end

+ 3 - 3
spec/factories/authorization.rb

@@ -2,9 +2,9 @@
 
 FactoryBot.define do
   factory :authorization do
-    transient do
-      user { create(:customer) }
-    end
+    uid      { Faker::Number.number(digits: 10) }
+    user     { create(:customer) }
+    provider { 'foo' }
 
     factory :twitter_authorization do
       provider { 'twitter' }

+ 66 - 2
spec/models/authorization_spec.rb

@@ -3,9 +3,9 @@
 require 'rails_helper'
 
 RSpec.describe Authorization, type: :model do
-  subject(:authorization) { create(:twitter_authorization) }
-
   describe 'User assets' do
+    subject(:authorization) { create(:twitter_authorization) }
+
     it 'does update assets after new authorizations created' do
       authorization.user.assets({})
       create(:twitter_authorization, provider: 'twitter2', user: authorization.user)
@@ -13,4 +13,68 @@ RSpec.describe Authorization, type: :model do
       expect(assets[:User][authorization.user.id]['accounts'].keys.count).to eq(2)
     end
   end
+
+  describe 'Account linking notification', sends_notification_emails: true do
+    subject(:authorization) { create(:authorization, user: agent, provider: 'github') }
+
+    let(:agent) { create(:agent) }
+
+    shared_examples 'sending out email notification' do
+      it 'sends out an email notification' do
+        check_notification do
+          authorization
+
+          sent(
+            template: 'user_auth_provider',
+            user:     authorization.user,
+            objects:  hash_including({ user: authorization.user, provider: authorization.provider })
+          )
+        end
+      end
+    end
+
+    shared_examples 'not sending out email notification' do
+      it 'does not send out an email notification' do
+        check_notification do
+          authorization
+
+          not_sent(
+            template: 'user_auth_provider',
+            user:     authorization.user,
+            objects:  hash_including({ user: authorization.user, provider: authorization.provider })
+          )
+        end
+      end
+    end
+
+    context 'with setting turned on' do
+      before do
+        Setting.set('auth_third_party_linking_notification', true)
+      end
+
+      context 'when linking with an existing account' do
+        it_behaves_like 'sending out email notification'
+
+        context 'when user has no email address' do
+          let(:agent) { create(:agent, email: '') }
+
+          it_behaves_like 'not sending out email notification'
+        end
+      end
+
+      context 'when creating a new account' do
+        let(:agent) { create(:agent, source: 'github') }
+
+        it_behaves_like 'not sending out email notification'
+      end
+    end
+
+    context 'with setting turned off' do
+      before do
+        Setting.set('auth_third_party_linking_notification', false)
+      end
+
+      it_behaves_like 'not sending out email notification'
+    end
+  end
 end