2020-10-27 00:50:13 +08:00
# frozen_string_literal: true
2011-02-01 09:57:29 +08:00
#
2017-04-28 04:06:18 +08:00
# Copyright (C) 2011 - present Instructure, Inc.
2011-02-01 09:57:29 +08:00
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
module SIS
2011-10-13 07:28:30 +08:00
class UserImporter < BaseImporter
2019-06-21 11:59:07 +08:00
def process ( messages , login_only : false )
2018-02-26 22:49:15 +08:00
importer = Work . new ( @batch , @root_account , @logger , messages )
2011-09-07 02:07:54 +08:00
User . skip_updating_account_associations do
2011-10-13 07:28:30 +08:00
User . process_as_sis ( @sis_options ) do
Pseudonym . process_as_sis ( @sis_options ) do
2011-10-11 06:31:18 +08:00
yield importer
while importer . any_left_to_process?
2019-06-21 11:59:07 +08:00
importer . process_batch ( login_only : login_only )
2011-10-11 06:31:18 +08:00
end
2011-09-22 01:36:45 +08:00
end
2011-02-01 09:57:29 +08:00
end
end
2021-09-23 00:25:11 +08:00
User . update_account_associations ( importer . users_to_add_account_associations , :incremental = > true , :precalculated_associations = > { @root_account . id = > 0 } )
2011-09-07 02:07:54 +08:00
User . update_account_associations ( importer . users_to_update_account_associations )
2021-09-23 00:25:11 +08:00
importer . pseudos_to_set_sis_batch_ids . in_groups_of ( 1000 , false ) { | ids | Pseudonym . where ( id : ids ) . update_all ( sis_batch_id : @batch . id ) }
2018-06-03 01:27:38 +08:00
SisBatchRollBackData . bulk_insert_roll_back_data ( importer . roll_back_data )
2020-02-11 00:24:28 +08:00
2018-05-12 19:50:01 +08:00
importer . success_count
2011-02-01 09:57:29 +08:00
end
2011-09-07 02:07:54 +08:00
class Work
attr_accessor :success_count , :users_to_set_sis_batch_ids ,
2021-09-23 00:25:11 +08:00
:pseudos_to_set_sis_batch_ids , :users_to_add_account_associations ,
:users_to_update_account_associations , :roll_back_data
2011-09-07 02:07:54 +08:00
2018-02-26 22:49:15 +08:00
def initialize ( batch , root_account , logger , messages )
2014-03-19 23:57:19 +08:00
@batch = batch
2011-09-07 02:07:54 +08:00
@root_account = root_account
@logger = logger
@batched_users = [ ]
@messages = messages
@success_count = 0
2018-05-12 19:50:01 +08:00
@roll_back_data = [ ]
2011-09-07 02:07:54 +08:00
@users_to_set_sis_batch_ids = [ ]
@pseudos_to_set_sis_batch_ids = [ ]
@users_to_add_account_associations = [ ]
@users_to_update_account_associations = [ ]
2015-07-30 05:11:28 +08:00
@authentication_providers = { }
2011-09-07 02:07:54 +08:00
end
2011-05-10 05:24:05 +08:00
2017-04-05 04:41:32 +08:00
# Pass a single instance of SIS::Models::User
2019-06-21 11:59:07 +08:00
def add_user ( user , login_only : false )
2017-04-05 04:41:32 +08:00
raise ImportError , " No user_id given for a user " if user . user_id . blank?
raise ImportError , " No login_id given for user #{ user . user_id } " if user . login_id . blank?
2021-10-22 05:46:36 +08:00
raise ImportError , " Improper status for user #{ user . user_id } " unless user . status . match? ( / \ A(active|suspended|deleted) /i )
return if @batch . skip_deletes? && user . status . match? ( / deleted /i )
2011-05-13 03:58:47 +08:00
2019-06-21 11:59:07 +08:00
if login_only && user . existing_user_id . blank? && user . existing_integration_id . blank? && user . existing_canvas_user_id . blank?
raise ImportError , I18n . t ( " No existing user provided for login with SIS ID %{user_id} " , user_id : user . user_id )
end
2017-04-05 04:41:32 +08:00
@batched_users << user
2019-06-21 11:59:07 +08:00
process_batch ( login_only : login_only ) if @batched_users . size > = Setting . get ( " sis_user_batch_size " , " 100 " ) . to_i
2011-09-07 02:07:54 +08:00
end
2011-05-04 03:21:18 +08:00
2011-09-07 02:07:54 +08:00
def any_left_to_process?
return @batched_users . size > 0
end
2017-12-21 07:03:28 +08:00
def infer_user_name ( user_row , prior_name = nil )
if user_row . full_name . present?
user_row . full_name
elsif user_row . first_name . present? || user_row . last_name . present?
[ user_row . first_name , user_row . last_name ] . join ( ' ' )
elsif prior_name . present?
prior_name
elsif user_row . sortable_name . present?
user_row . sortable_name
elsif user_row . short_name . present?
user_row . short_name
elsif user_row . login_id . present?
user_row . login_id
else
raise ImportError , " No name given for user "
end
end
def infer_sortable_name ( user_row , prior_sortable_name = nil )
2018-02-14 00:50:00 +08:00
if user_row . sortable_name . present?
2017-12-21 07:03:28 +08:00
user_row . sortable_name
2018-02-14 00:50:00 +08:00
elsif user_row . full_name . present?
nil # force User model to infer sortable name from the full name
2017-12-21 07:03:28 +08:00
elsif user_row . last_name . present? || user_row . first_name . present?
[ user_row . last_name , user_row . first_name ] . join ( ', ' )
else
prior_sortable_name
end
end
2021-10-22 05:46:36 +08:00
VALID_STATUSES = %w[ active suspended deleted ] . freeze
private_constant :VALID_STATUSES
2019-06-21 11:59:07 +08:00
def process_batch ( login_only : false )
2011-09-07 02:07:54 +08:00
return unless any_left_to_process?
2021-09-23 00:25:11 +08:00
2021-10-22 05:46:36 +08:00
until @batched_users . empty?
2018-05-30 23:21:53 +08:00
user_row = @batched_users . shift
pseudo = @root_account . pseudonyms . where ( sis_user_id : user_row . user_id . to_s ) . take
2020-08-31 15:01:46 +08:00
if user_row . authentication_provider_id . present?
unless @authentication_providers . key? ( user_row . authentication_provider_id )
begin
@authentication_providers [ user_row . authentication_provider_id ] =
@root_account . authentication_providers . active . find ( user_row . authentication_provider_id )
rescue ActiveRecord :: RecordNotFound
@authentication_providers [ user_row . authentication_provider_id ] = nil
end
end
pseudo_by_login = @root_account . pseudonyms . active . by_unique_id ( user_row . login_id ) . where ( authentication_provider_id : @authentication_providers [ user_row . authentication_provider_id ] ) . take
else
pseudo_by_login = @root_account . pseudonyms . active . by_unique_id ( user_row . login_id ) . take
end
2019-03-29 11:03:11 +08:00
pseudo_by_integration = nil
pseudo_by_integration = @root_account . pseudonyms . where ( integration_id : user_row . integration_id . to_s ) . take if user_row . integration_id . present?
2021-10-22 05:46:36 +08:00
status = user_row . status . downcase
status = 'active' unless VALID_STATUSES . include? ( status )
2018-05-30 23:21:53 +08:00
pseudo || = pseudo_by_login
2021-10-22 05:46:36 +08:00
if pseudo_by_integration && status != 'deleted' && pseudo_by_integration != pseudo
2019-06-21 11:59:07 +08:00
id_message = pseudo_by_integration . sis_user_id ? I18n . t ( 'SIS ID' ) : I18n . t ( 'Canvas ID' )
2019-03-29 11:03:11 +08:00
user_id = pseudo_by_integration . sis_user_id || pseudo_by_integration . user_id
2021-09-27 23:58:39 +08:00
message = I18n . t ( " An existing Canvas user with the %{user_id} has already claimed %{other_user_id}'s requested integration_id, skipping " , user_id : " #{ id_message } #{ user_id } " , other_user_id : user_row . user_id )
2019-08-24 05:08:21 +08:00
@messages << SisBatch . build_error ( user_row . csv , message , sis_batch : @batch , row : user_row . lineno , row_info : user_row . row )
2019-03-29 11:03:11 +08:00
next
end
2018-05-30 23:21:53 +08:00
if pseudo
2019-06-21 11:59:07 +08:00
if login_only
message = I18n . t ( " An existing Canvas user with the SIS ID %{user_id} or login of %{login} already exists, skipping " , user_id : user_row . user_id , login : user_row . login_id )
2019-08-24 05:08:21 +08:00
@messages << SisBatch . build_error ( user_row . csv , message , sis_batch : @batch , row : user_row . lineno , row_info : user_row . row )
2019-06-21 11:59:07 +08:00
next
end
2018-05-30 23:21:53 +08:00
if pseudo . sis_user_id && pseudo . sis_user_id != user_row . user_id
2021-06-25 21:35:12 +08:00
if @batch . options [ :update_sis_id_if_login_claimed ]
pseudo . sis_user_id = user_row . user_id
else
message = I18n . t ( " An existing Canvas user with the SIS ID %{user_id} has already claimed %{other_user_id}'s user_id requested login information, skipping " , user_id : pseudo . sis_user_id , other_user_id : user_row . user_id )
@messages << SisBatch . build_error ( user_row . csv , message , sis_batch : @batch , row : user_row . lineno , row_info : user_row . row )
next
end
2018-05-30 23:21:53 +08:00
end
2021-10-22 05:46:36 +08:00
if pseudo_by_login && ( ( pseudo != pseudo_by_login && status != 'deleted' ) ||
2018-05-30 23:21:53 +08:00
! Pseudonym . where ( " LOWER(?)=LOWER(?) " , pseudo . unique_id , user_row . login_id ) . exists? )
id_message = pseudo_by_login . sis_user_id ? 'SIS ID' : 'Canvas ID'
user_id = pseudo_by_login . sis_user_id || pseudo_by_login . user_id
2021-09-27 23:58:39 +08:00
message = I18n . t ( " An existing Canvas user with the %{user_id} has already claimed %{other_user_id}'s user_id requested login information, skipping " , user_id : " #{ id_message } #{ user_id } " , other_user_id : user_row . user_id )
2019-08-24 05:08:21 +08:00
@messages << SisBatch . build_error ( user_row . csv , message , sis_batch : @batch , row : user_row . lineno , row_info : user_row . row )
2018-05-30 23:21:53 +08:00
next
end
2011-09-07 02:07:54 +08:00
2018-05-30 23:21:53 +08:00
user = pseudo . user
unless user . stuck_sis_fields . include? ( :name )
user . name = infer_user_name ( user_row , user . name )
end
unless user . stuck_sis_fields . include? ( :sortable_name )
user . sortable_name = infer_sortable_name ( user_row , user . sortable_name )
end
unless user . stuck_sis_fields . include? ( :short_name )
2017-04-05 04:41:32 +08:00
user . short_name = user_row . short_name if user_row . short_name . present?
2011-09-07 02:07:54 +08:00
end
2018-05-30 23:21:53 +08:00
else
2019-06-21 11:59:07 +08:00
if login_only
if user_row . root_account_id . present?
2020-08-05 17:52:27 +08:00
root_account = root_account_from_id ( user_row . root_account_id , user_row )
2019-06-21 11:59:07 +08:00
next unless root_account
else
root_account = @root_account
end
pseudo = existing_login ( user_row , root_account )
if pseudo . nil?
message = I18n . t ( " Could not find the existing user for login with SIS ID %{user_id}, skipping " , user_id : user_row . user_id )
2019-08-24 05:08:21 +08:00
@messages << SisBatch . build_error ( user_row . csv , message , sis_batch : @batch , row : user_row . lineno , row_info : user_row . row )
2019-06-21 11:59:07 +08:00
next
elsif pseudo . attributes . slice ( * user_row . login_hash . keys ) != user_row . login_hash
message = I18n . t ( " An existing user does not match existing user ids provided for login with SIS ID %{user_id}, skipping " , user_id : user_row . user_id )
2019-08-24 05:08:21 +08:00
@messages << SisBatch . build_error ( user_row . csv , message , sis_batch : @batch , row : user_row . lineno , row_info : user_row . row )
2019-06-21 11:59:07 +08:00
next
else
user = pseudo . user
pseudo = Pseudonym . new
end
else
user = nil
pseudo = Pseudonym . new
user = other_user ( user_row , pseudo ) if user_row . integration_id . present?
unless user
user = User . new
user . name = infer_user_name ( user_row )
user . sortable_name = infer_sortable_name ( user_row )
user . short_name = user_row . short_name if user_row . short_name . present?
end
2019-03-29 11:03:11 +08:00
end
2018-05-30 23:21:53 +08:00
end
2011-09-07 02:07:54 +08:00
2018-05-30 23:21:53 +08:00
# we just leave all users registered now
# since we've deleted users though, we need to do this to be
# backwards compatible with the data
user . workflow_state = 'registered'
2011-10-29 07:40:37 +08:00
2018-05-30 23:21:53 +08:00
should_add_account_associations = false
should_update_account_associations = false
2016-08-20 04:18:15 +08:00
2020-11-03 04:33:10 +08:00
if user_row . pronouns . present? && ! user . stuck_sis_fields . include? ( :pronouns )
user . pronouns = ( user_row . pronouns == '<delete>' ) ? nil : user_row . pronouns
end
2020-05-06 03:56:05 +08:00
2021-10-08 14:10:46 +08:00
if user_row . declared_user_type . present?
pseudo . declared_user_type = user_row . declared_user_type == '<delete>' ? nil : user_row . declared_user_type
end
2021-10-22 05:46:36 +08:00
if status == 'deleted' && ! user . new_record?
2018-05-30 23:21:53 +08:00
if user . id == @batch & . user_id
message = " Can't remove yourself user_id ' #{ user_row . user_id } ' "
2019-08-24 05:08:21 +08:00
@messages << SisBatch . build_error ( user_row . csv , message , sis_batch : @batch , row : user_row . lineno , row_info : user_row . row )
2018-05-30 23:21:53 +08:00
next
2011-09-07 02:07:54 +08:00
end
2011-05-04 03:21:18 +08:00
2020-08-05 22:39:31 +08:00
# if the pseudonym is already deleted, we're done.
next if pseudo . workflow_state == 'deleted'
2018-05-30 23:21:53 +08:00
# if this user is deleted and there are no more active logins,
# we're going to delete any enrollments for this root account and
# delete this pseudonym.
should_update_account_associations = remove_enrollments_if_last_login ( user , user_row . user_id )
end
pseudo . unique_id = user_row . login_id unless pseudo . stuck_sis_fields . include? ( :unique_id )
if user_row . authentication_provider_id . present?
unless ( pseudo . authentication_provider = @authentication_providers [ user_row . authentication_provider_id ] )
message = " unrecognized authentication provider #{ user_row . authentication_provider_id } for #{ user_row . user_id } , skipping "
2019-08-24 05:08:21 +08:00
@messages << SisBatch . build_error ( user_row . csv , message , sis_batch : @batch , row : user_row . lineno , row_info : user_row . row )
2018-03-10 07:11:51 +08:00
next
2017-05-19 06:44:03 +08:00
end
2018-05-30 23:21:53 +08:00
else
pseudo . authentication_provider = nil
end
pseudo . sis_user_id = user_row . user_id
pseudo . integration_id = user_row . integration_id if user_row . integration_id . present?
pseudo . account = @root_account
2021-11-09 17:40:28 +08:00
unless pseudo . stuck_sis_fields . include? ( :workflow_state )
pseudo . workflow_state = status
pseudo . deleted_at = Time . now . utc if status == 'deleted'
end
2021-10-22 05:46:36 +08:00
if pseudo . new_record? && status != 'deleted'
2018-05-30 23:21:53 +08:00
should_add_account_associations = true
elsif pseudo . workflow_state_changed?
2021-10-22 05:46:36 +08:00
if status == 'deleted'
2018-05-30 23:21:53 +08:00
should_update_account_associations = true
2021-10-22 05:46:36 +08:00
else
should_add_account_associations = true
2011-10-29 07:40:37 +08:00
end
2018-05-30 23:21:53 +08:00
end
2011-10-29 07:40:37 +08:00
2018-05-30 23:21:53 +08:00
# if a password is provided, use it only if this is a new user, or the user hasn't changed the password in canvas *AND* the incoming password has changed
# otherwise the persistence_token will change even though we're setting to the same password, logging the user out
2021-09-27 23:58:39 +08:00
if ! user_row . password . blank? && ( pseudo . new_record? || ( pseudo . password_auto_generated && ! pseudo . valid_password? ( user_row . password ) ) )
2018-05-30 23:21:53 +08:00
pseudo . password = user_row . password
pseudo . password_confirmation = user_row . password
pseudo . password_auto_generated = true
end
pseudo . sis_ssha = user_row . ssha_password if ! user_row . ssha_password . blank?
pseudo . reset_persistence_token if pseudo . sis_ssha_changed? && pseudo . password_auto_generated
user_touched = false
2019-09-17 23:25:31 +08:00
user . sortable_name_explicitly_set = true if user_row . sortable_name . present?
2018-05-30 23:21:53 +08:00
begin
User . transaction ( :requires_new = > true ) do
if user . changed?
user_touched = true
if ! user . save && user . errors . size > 0
message = generate_user_warning ( user . errors . first . join ( " " ) , user_row . user_id , user_row . login_id )
raise ImportError , message
2011-05-13 03:58:47 +08:00
end
2018-05-30 23:21:53 +08:00
elsif @batch
@users_to_set_sis_batch_ids << user . id
end
pseudo . user_id = user . id
if pseudo . changed?
pseudo . sis_batch_id = @batch . id if @batch
if pseudo . save_without_broadcasting
p_data = SisBatchRollBackData . build_data ( sis_batch : @batch , context : pseudo )
@roll_back_data << p_data if p_data
elsif pseudo . errors . size > 0
message = generate_user_warning ( pseudo . errors . first . join ( " " ) , user_row . user_id , user_row . login_id )
raise ImportError , message
2011-09-07 02:07:54 +08:00
end
end
end
2018-05-30 23:21:53 +08:00
rescue ImportError
2019-08-24 05:08:21 +08:00
@messages << SisBatch . build_error ( user_row . csv , message , sis_batch : @batch , row : user_row . lineno , row_info : user_row . row )
2018-05-30 23:21:53 +08:00
next
rescue = > e
# something broke
error = Canvas :: Errors . capture_exception ( :sis_import , e )
er = error [ :error_report ]
message = generate_user_warning ( " Something broke with this user. Contact Support with ErrorReport id: #{ er } " , user_row . user_id , user_row . login_id )
2019-08-24 05:08:21 +08:00
@messages << SisBatch . build_error ( user_row . csv , message , sis_batch : @batch , row : user_row . lineno , backtrace : e . backtrace , row_info : user_row . row )
2018-05-30 23:21:53 +08:00
next
end
2011-02-01 09:57:29 +08:00
2018-05-30 23:21:53 +08:00
@users_to_add_account_associations << user . id if should_add_account_associations
@users_to_update_account_associations << user . id if should_update_account_associations
2011-10-29 07:40:37 +08:00
2018-05-30 23:21:53 +08:00
if user_row . email . present? && EmailAddressValidator . valid? ( user_row . email )
# find all CCs for this user, and active conflicting CCs for all users
# unless we're deleting this user, then only find CCs for this user
2020-10-29 06:42:53 +08:00
ccs = [ ]
user . shard . activate do
# ^ maybe after switchman supports OR conditions we can not do this?
# as it is, this scope gets evaluated on the current shard instead of the user shard
# and that can lead to failing to find the matching communication channel.
2021-10-22 05:46:36 +08:00
cc_scope = if status == 'deleted'
2021-09-23 00:25:11 +08:00
user . communication_channels
2021-10-22 05:46:36 +08:00
else
CommunicationChannel . where ( " workflow_state='active' OR user_id=? " , user )
2021-09-23 00:25:11 +08:00
end
2020-10-29 06:42:53 +08:00
cc_scope = cc_scope . email . by_path ( user_row . email )
limit = Setting . get ( " merge_candidate_search_limit " , " 100 " ) . to_i
ccs = cc_scope . limit ( limit + 1 ) . to_a
2020-11-03 01:19:36 +08:00
if ccs . count > limit
ccs = cc_scope . where ( :user_id = > user ) . to_a # don't bother with merge candidates anymore
end
2018-05-30 23:21:53 +08:00
end
# sis_cc could be set from the previous user, if we're not on a transaction boundary,
# and the previous user had an sis communication channel, and this user doesn't have one
# then it would have "stolen" to sis_cc from the previous user
sis_cc = nil
sis_cc = ccs . find { | cc | cc . id == pseudo . sis_communication_channel_id } if pseudo . sis_communication_channel_id
# Have to explicitly load the old sis communication channel, in case it changed (should only happen if user_id got messed up)
sis_cc || = pseudo . sis_communication_channel
# search for active/unconfirmed channels first, so we don't try to resurrect a conflicting cc
2021-09-23 00:25:11 +08:00
other_cc = ccs . find { | cc | cc . user_id == user . id && cc . id != sis_cc . try ( :id ) && ( cc . active? || cc . unconfirmed? ) }
2018-05-30 23:21:53 +08:00
other_cc || = ccs . find { | cc | cc . user_id == user . id && cc . id != sis_cc . try ( :id ) }
# Handle the case where the SIS CC changes to match an already existing CC
if sis_cc && other_cc
sis_cc . destroy
2012-01-04 07:40:03 +08:00
sis_cc = nil
2011-09-07 02:07:54 +08:00
end
2020-10-03 03:35:20 +08:00
cc = sis_cc || other_cc || user . communication_channels . new
2018-05-30 23:21:53 +08:00
cc . user_id = user . id
cc . pseudonym_id = pseudo . id
cc . path = user_row . email
cc . bounce_count = 0 if cc . path_changed?
2021-10-22 05:46:36 +08:00
cc . workflow_state = status == 'deleted' ? 'retired' : 'active'
2018-05-30 23:21:53 +08:00
newly_active = cc . path_changed? || ( cc . active? && cc . workflow_state_changed? )
if cc . changed?
2020-10-29 06:42:53 +08:00
if cc . valid? && cc . save_without_broadcasting
2018-05-30 23:21:53 +08:00
cc_data = SisBatchRollBackData . build_data ( sis_batch : @batch , context : cc )
@roll_back_data << cc_data if cc_data
2013-03-05 08:00:01 +08:00
else
2018-05-30 23:21:53 +08:00
msg = " An email did not pass validation "
msg += " ( " + " #{ user_row . email } , error: "
msg += cc . errors . full_messages . join ( " , " ) + " ) "
2013-03-05 08:00:01 +08:00
raise ImportError , msg
end
2018-05-30 23:21:53 +08:00
user . touch unless user_touched
user . clear_email_cache!
end
pseudo . sis_communication_channel_id = pseudo . communication_channel_id = cc . id
2021-05-11 04:09:23 +08:00
if newly_active && @root_account . feature_enabled? ( :self_service_user_merge )
2018-05-30 23:21:53 +08:00
user_ids = ccs . map ( & :user_id )
pseudo_scope = Pseudonym . active . where ( user_id : user_ids ) . group ( :user_id )
active_pseudo_counts = pseudo_scope . count
sis_pseudo_counts = pseudo_scope . where ( 'account_id = ? AND sis_user_id IS NOT NULL' , @root_account ) . count
2021-11-04 05:36:34 +08:00
other_ccs = ccs . reject do | other |
cc_user_id = other . user_id
2018-05-30 23:21:53 +08:00
same_user = cc_user_id == user . id
no_active_pseudos = active_pseudo_counts . fetch ( cc_user_id , 0 ) == 0
active_sis_pseudos = sis_pseudo_counts . fetch ( cc_user_id , 0 ) != 0
same_user || no_active_pseudos || active_sis_pseudos
2021-11-04 05:36:34 +08:00
end
2018-05-30 23:21:53 +08:00
unless other_ccs . empty?
cc . send_merge_notification!
end
2011-04-30 04:03:25 +08:00
end
2018-05-30 23:21:53 +08:00
elsif user_row . email . present? && EmailAddressValidator . valid? ( user_row . email ) == false
message = " The email address associated with user ' #{ user_row . user_id } ' is invalid (email: ' #{ user_row . email } ') "
2019-08-24 05:08:21 +08:00
@messages << SisBatch . build_error ( user_row . csv , message , sis_batch : @batch , row : user_row . lineno , row_info : user_row . row )
2018-05-30 23:21:53 +08:00
next
end
2011-09-07 02:07:54 +08:00
2018-05-30 23:21:53 +08:00
if pseudo . changed?
pseudo . sis_batch_id = user_row . sis_batch_id if user_row . sis_batch_id
pseudo . sis_batch_id = @batch . id if @batch
if pseudo . valid?
pseudo . save_without_broadcasting
else
msg = " A user did not pass validation "
msg += " ( " + " user: #{ user_row . user_id } , error: "
msg += pseudo . errors . full_messages . join ( " , " ) + " ) "
raise ImportError , msg
end
elsif @batch && pseudo . sis_batch_id != @batch . id
@pseudos_to_set_sis_batch_ids << pseudo . id
2011-04-29 04:52:57 +08:00
end
2018-05-30 23:21:53 +08:00
maybe_write_roll_back_data
@success_count += 1
2011-02-01 09:57:29 +08:00
end
end
2016-01-28 09:13:56 +08:00
2019-06-21 11:59:07 +08:00
def existing_login ( user_row , root_account )
root_account . shard . activate do
login = nil
user_row . login_hash . each do | attr , value |
login || = root_account . pseudonyms . active . where ( attr = > value ) . take
end
login
end
end
2019-03-29 11:03:11 +08:00
def other_user ( _user_row , _pseudo ) ; end
2020-08-05 17:52:27 +08:00
def root_account_from_id ( _root_account_sis_id , _user_row ) ; end
2019-06-21 11:59:07 +08:00
2018-05-12 19:50:01 +08:00
def maybe_write_roll_back_data
if @roll_back_data . count > 1000
SisBatchRollBackData . bulk_insert_roll_back_data ( @roll_back_data )
@roll_back_data = [ ]
end
end
2017-09-30 04:22:44 +08:00
def remove_enrollments_if_last_login ( user , user_id )
2018-05-12 19:50:01 +08:00
return false if @root_account . pseudonyms . active . where ( user_id : user ) . where ( " sis_user_id != ? OR sis_user_id IS NULL " , user_id ) . exists?
2021-09-23 00:25:11 +08:00
enrollments = @root_account . enrollments . active . where ( user_id : user )
. select ( :id , :type , :course_id , :course_section_id , :user_id , :workflow_state ) . to_a
2018-05-12 19:50:01 +08:00
if enrollments . any?
Enrollment . where ( id : enrollments . map ( & :id ) ) . update_all ( updated_at : Time . now . utc , workflow_state : 'deleted' )
2019-07-29 21:43:58 +08:00
EnrollmentState . where ( enrollment_id : enrollments . map ( & :id ) ) . update_all ( state : 'deleted' , state_is_current : true , updated_at : Time . now . utc )
2018-05-12 19:50:01 +08:00
e_data = SisBatchRollBackData . build_dependent_data ( sis_batch : @batch , contexts : enrollments , updated_state : 'deleted' )
@roll_back_data . push ( * e_data ) if e_data
2017-09-30 04:22:44 +08:00
end
2019-03-07 23:15:21 +08:00
student_enrollments = enrollments . select ( & :student? )
if student_enrollments . any?
observers = user . linked_observers . active . linked_through_root_account ( @root_account ) . to_a
2021-09-23 00:25:11 +08:00
observer_enrollments = observers . map { | o | student_enrollments . map { | se | se . linked_enrollment_for ( o ) } } . flatten . compact
2019-03-07 23:15:21 +08:00
if observer_enrollments . any?
Enrollment . where ( id : observer_enrollments . map ( & :id ) ) . update_all ( updated_at : Time . now . utc , workflow_state : 'deleted' )
2019-07-29 21:43:58 +08:00
EnrollmentState . where ( enrollment_id : observer_enrollments . map ( & :id ) ) . update_all ( state : 'deleted' , state_is_current : true , updated_at : Time . now . utc )
2019-03-07 23:15:21 +08:00
oe_data = SisBatchRollBackData . build_dependent_data ( sis_batch : @batch , contexts : observer_enrollments , updated_state : 'deleted' )
@roll_back_data . push ( * oe_data ) if oe_data
end
end
2018-05-12 19:50:01 +08:00
gms = @root_account . all_group_memberships . active . where ( user_id : user ) . select ( :id , :workflow_state ) . to_a
gm_data = SisBatchRollBackData . build_dependent_data ( sis_batch : @batch , contexts : gms , updated_state : 'deleted' )
@roll_back_data . push ( * gm_data ) if gm_data
admins = user . account_users . active . shard ( @root_account ) . where ( account_id : @root_account . all_accounts ) . select ( :id , :workflow_state ) . to_a
a_data = SisBatchRollBackData . build_dependent_data ( sis_batch : @batch , contexts : admins , updated_state : 'deleted' )
@roll_back_data . push ( * a_data ) if a_data
root_admins = user . account_users . active . shard ( @root_account ) . where ( account_id : @root_account ) . select ( :id , :workflow_state ) . to_a
r_data = SisBatchRollBackData . build_dependent_data ( sis_batch : @batch , contexts : root_admins , updated_state : 'deleted' )
@roll_back_data . push ( * r_data ) if r_data
2017-09-30 04:22:44 +08:00
2018-05-12 19:50:01 +08:00
d = enrollments . count
2021-10-12 07:15:41 +08:00
gm = @root_account . all_group_memberships . active . where ( user_id : user )
2021-09-23 00:25:11 +08:00
. update_all ( updated_at : Time . now . utc , workflow_state : 'deleted' )
2021-10-12 07:15:41 +08:00
if gm > 0
d += gm
@root_account . all_groups . where ( leader_id : user ) . update_all ( leader_id : nil )
end
2021-09-23 00:25:11 +08:00
d += user . account_users . active . shard ( @root_account ) . where ( account_id : @root_account . all_accounts )
. update_all ( updated_at : Time . now . utc , workflow_state : 'deleted' )
d += user . account_users . active . shard ( @root_account ) . where ( account_id : @root_account )
. update_all ( updated_at : Time . now . utc , workflow_state : 'deleted' )
2017-09-30 04:22:44 +08:00
if d > 0
should_update_account_associations = true
end
should_update_account_associations
end
2016-01-28 09:13:56 +08:00
private
2018-03-17 02:49:16 +08:00
def generate_user_warning ( message , user_id , login_id )
2021-11-11 23:47:19 +08:00
generate_readable_error_message (
2016-10-01 02:30:39 +08:00
message : message ,
user_id : user_id ,
login_id : login_id
)
end
2016-01-28 09:13:56 +08:00
ERRORS_TO_REASONS = {
'unique_id is invalid' = > " Invalid login_id: '%{login_id}' " ,
} . freeze
2021-11-11 03:05:31 +08:00
DEFAULT_REASON = 'Unknown reason: %{message}'
2016-01-28 09:13:56 +08:00
def generate_readable_error_message ( options )
response = ERRORS_TO_REASONS . fetch ( options [ :message ] ) { DEFAULT_REASON }
reason = format ( response , options )
2021-11-11 23:47:19 +08:00
" Could not save the user with user_id: ' #{ options [ :user_id ] } '. " +
" #{ reason } "
2016-01-28 09:13:56 +08:00
end
2011-02-01 09:57:29 +08:00
end
end
2011-04-29 04:52:57 +08:00
end