# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ class Authorization < ApplicationModel belongs_to :user, optional: true after_create :delete_user_cache, :notification_send after_update :delete_user_cache after_destroy :delete_user_cache validates :user_id, presence: true validates :uid, presence: true, uniqueness: { case_sensitive: true, scope: :provider } validates :provider, presence: true def self.find_from_hash(hash) auth = Authorization.find_by(provider: hash['provider'], uid: hash['uid']) if auth # update auth tokens auth.update!( token: hash['credentials']['token'], secret: hash['credentials']['secret'] ) # update username of auth entry if empty if !auth.username && hash['info']['nickname'].present? auth.update!( username: hash['info']['nickname'], ) end # update firstname/lastname if needed user = User.find(auth.user_id) if user.firstname.blank? && user.lastname.blank? if hash['info']['first_name'].present? && hash['info']['last_name'].present? user.firstname = hash['info']['first_name'] user.lastname = hash['info']['last_name'] elsif hash['info']['display_name'].present? user.firstname = hash['info']['display_name'] end end # update image if needed if hash['info']['image'].present? avatar = Avatar.add( object: 'User', o_id: user.id, url: hash['info']['image'], source: hash['provider'], deletable: true, updated_by_id: user.id, created_by_id: user.id, ) if avatar && user.image != avatar.store_hash user.image = avatar.store_hash end end if user.changed? user.save end end auth end def self.create_from_hash(hash, user = nil) auth_provider = "#{PROVIDER_CLASS_PREFIX}#{hash['provider'].camelize}".constantize.new(hash, user) # save/update avatar if hash['info'].present? && hash['info']['image'].present? avatar = Avatar.add( object: 'User', o_id: auth_provider.user.id, url: hash['info']['image'], source: auth_provider.name, deletable: true, updated_by_id: auth_provider.user.id, created_by_id: auth_provider.user.id, ) # update user link if avatar && auth_provider.user.image != avatar.store_hash auth_provider.user.image = avatar.store_hash auth_provider.user.save end end Authorization.create!( user: auth_provider.user, uid: auth_provider.uid, username: hash['info']['nickname'] || hash['info']['username'] || hash['info']['name'] || hash['info']['email'] || hash['username'], provider: auth_provider.name, token: hash['credentials']['token'], secret: hash['credentials']['secret'] ) end private PROVIDER_CLASS_PREFIX = 'Authorization::Provider::'.freeze def delete_user_cache return if !user 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_name(provider), } ) end def provider_name(provider) return saml_display_name(provider) if provider == 'saml' provider_title(provider) end # In case of SAML authentication provider, there is a separate display name setting that may be defined. def saml_display_name(provider) begin Setting.get('auth_saml_credentials')['display_name'] rescue provider_title(provider) end end def provider_title(provider) begin Setting.find_by(name: "auth_#{provider}").preferences['title_i18n'].shift rescue provider end end end