require 'active_record' require 'cartodb-common' require_dependency 'cartodb/errors' require_dependency 'carto/user_authenticator' module Carto class Invitation < ActiveRecord::Base # Because of an activerecord-postgresql-array bug that makes array # insertions unusable we can't set _users_emails mandatory on construction, # so we create a creator enforcing desired behaviour. # This will be fixed when we upgrade Ruby and Rails # validates :users_emails, :welcome_text, presence: true validates :inviter_user, :organization, :welcome_text, presence: true validates :users_emails, email: true validate :users_emails_not_taken belongs_to :inviter_user, class_name: Carto::User belongs_to :organization private_class_method :new def self.create_new(inviter_user, users_emails, welcome_text, viewer) raise CartoDB::InvalidUser.new("Only admins can create invitations") unless inviter_user.organization_admin? # ActiveRecord validation for all values invitation = new(inviter_user: inviter_user, organization: inviter_user.organization, users_emails: users_emails, welcome_text: welcome_text, viewer: viewer) return invitation unless invitation.valid? # Two-step creation workarounding array bug invitation = new(inviter_user: inviter_user, organization: inviter_user.organization, welcome_text: welcome_text, viewer: viewer) invitation.seed = Carto::Common::EncryptionService.make_token if invitation.save invitation.reload invitation.users_emails = users_emails invitation.used_emails = [] invitation.save ::Resque.enqueue(::Resque::OrganizationJobs::Mail::Invitation, invitation.id) end invitation end def self.query_with_valid_email(email) Carto::Invitation.where('? = ANY(users_emails)', email) end def self.query_with_unused_email(email) query_with_valid_email(email).where('? != ALL(used_emails)', email) end def token(email) Carto::Common::EncryptionService.encrypt(sha_class: Digest::SHA256, password: email, salt: seed) end def use(email, token) # reload and used_emails assignment is needed because otherwise # activerecord-postgresql-array won't update the invitations reload if users_emails.include?(email) && verify_token(token, email) if used_emails.include?(email) raise AlreadyUsedInvitationError.new("#{email} has already used the invitation") else self.used_emails = used_emails.push(email) save true end else false end end private def verify_token(token, email) Carto::Common::EncryptionService.verify(password: email, secure_password: token, salt: seed) end def users_emails_not_taken return unless users_emails users_emails.each do |email| errors[:users_emails] << "Existing user for #{email}" if Carto::User.find_by_email(email) end end end end class AlreadyUsedInvitationError < StandardError; end