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
2011-02-01 09:57:29 +08:00
2011-09-07 02:07:54 +08:00
def process ( updates_every , messages )
start = Time . now
2014-03-19 23:57:19 +08:00
importer = Work . new ( @batch , @root_account , @logger , updates_every , 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?
importer . process_batch
end
2011-09-22 01:36:45 +08:00
end
2011-02-01 09:57:29 +08:00
end
end
2011-09-07 02:07:54 +08:00
User . update_account_associations ( importer . users_to_add_account_associations , :incremental = > true , :precalculated_associations = > { @root_account . id = > 0 } )
User . update_account_associations ( importer . users_to_update_account_associations )
2013-09-05 23:58:09 +08:00
importer . pseudos_to_set_sis_batch_ids . in_groups_of ( 1000 , false ) do | batch |
2017-03-15 01:48:55 +08:00
Pseudonym . where ( :id = > batch ) . update_all ( :sis_batch_id = > @batch . id )
2013-09-05 23:58:09 +08:00
end if @batch
2011-09-07 02:07:54 +08:00
@logger . debug ( " Users took #{ Time . now - start } seconds " )
return importer . success_count
2011-02-01 09:57:29 +08:00
end
2011-09-07 02:07:54 +08:00
private
class Work
attr_accessor :success_count , :users_to_set_sis_batch_ids ,
:pseudos_to_set_sis_batch_ids , :users_to_add_account_associations ,
:users_to_update_account_associations
2014-03-19 23:57:19 +08:00
def initialize ( batch , root_account , logger , updates_every , messages )
@batch = batch
2011-09-07 02:07:54 +08:00
@root_account = root_account
@logger = logger
@updates_every = updates_every
@batched_users = [ ]
@messages = messages
@success_count = 0
@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
2017-04-05 09:07:49 +08:00
def add_user ( user )
2017-04-05 04:41:32 +08:00
@logger . debug ( " Processing User #{ user . to_a . inspect } " )
2011-05-13 03:58:47 +08:00
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?
raise ImportError , " Improper status for user #{ user . user_id } " unless user . status =~ / \ A(active|deleted) /i
2011-05-13 03:58:47 +08:00
2017-04-05 04:41:32 +08:00
@batched_users << user
2011-09-07 02:07:54 +08:00
process_batch if @batched_users . size > = @updates_every
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
def process_batch
return unless any_left_to_process?
transaction_timeout = Setting . get ( 'sis_transaction_seconds' , '1' ) . to_i . seconds
User . transaction do
tx_end_time = Time . now + transaction_timeout
user_row = nil
2011-09-27 07:19:39 +08:00
while ! @batched_users . empty? && tx_end_time > Time . now
user_row = @batched_users . shift
2011-09-07 02:07:54 +08:00
@logger . debug ( " Processing User #{ user_row . inspect } " )
2017-04-05 04:41:32 +08:00
pseudo = @root_account . pseudonyms . where ( sis_user_id : user_row . user_id . to_s ) . take
pseudo_by_login = @root_account . pseudonyms . active . by_unique_id ( user_row . login_id ) . take
2011-09-07 02:07:54 +08:00
pseudo || = pseudo_by_login
2017-04-05 04:41:32 +08:00
pseudo || = @root_account . pseudonyms . active . by_unique_id ( user_row . email ) . take if user_row . email . present?
2011-09-07 02:07:54 +08:00
2017-04-05 04:41:32 +08:00
status_is_active = ! ( user_row . status =~ / \ Adeleted /i )
2011-09-07 02:07:54 +08:00
if pseudo
2017-04-05 04:41:32 +08:00
if pseudo . sis_user_id && pseudo . sis_user_id != user_row . user_id
2017-04-13 03:07:36 +08:00
@messages << 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 )
2011-09-07 02:07:54 +08:00
next
end
2014-03-07 03:15:26 +08:00
if pseudo_by_login && ( pseudo != pseudo_by_login && status_is_active ||
2017-04-13 03:07:36 +08:00
! ActiveRecord :: Base . connection . select_value ( " SELECT 1 FROM #{ Pseudonym . quoted_table_name } WHERE #{ Pseudonym . to_lower_column ( Pseudonym . sanitize ( pseudo . unique_id ) ) } = #{ Pseudonym . to_lower_column ( Pseudonym . sanitize ( user_row . login_id ) ) } LIMIT 1 " ) )
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
@messages << 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 . to_s } " , other_user_id : user_row . user_id )
2011-09-07 02:07:54 +08:00
next
end
user = pseudo . user
2014-05-03 04:27:40 +08:00
unless user . stuck_sis_fields . include? ( :name )
2017-04-05 04:41:32 +08:00
user . name = " #{ user_row . first_name } #{ user_row . last_name } "
user . name = user_row . full_name if user_row . full_name . present?
2014-05-03 04:27:40 +08:00
end
2011-10-27 05:09:09 +08:00
unless user . stuck_sis_fields . include? ( :sortable_name )
2017-04-05 04:41:32 +08:00
user . sortable_name = user_row . last_name . present? && user_row . first_name . present? ? " #{ user_row . last_name } , #{ user_row . first_name } " : " #{ user_row . first_name } #{ user_row . last_name } "
user . sortable_name = nil if user_row . full_name . present? # force User model to infer sortable name from the full name
user . sortable_name = user_row . sortable_name if user_row . sortable_name . present?
2011-10-27 05:09:09 +08:00
end
2014-04-09 08:04:43 +08:00
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?
2014-04-09 08:04:43 +08:00
end
2011-09-07 02:07:54 +08:00
else
user = User . new
2017-04-05 04:41:32 +08:00
user . name = " #{ user_row . first_name } #{ user_row . last_name } "
user . name = user_row . full_name if user_row . full_name . present?
user . sortable_name = user_row . last_name . present? && user_row . first_name . present? ? " #{ user_row . last_name } , #{ user_row . first_name } " : " #{ user_row . first_name } #{ user_row . last_name } "
user . sortable_name = nil if user_row . full_name . present? # force User model to infer sortable name from the full name
user . sortable_name = user_row . sortable_name if user_row . sortable_name . present?
user . short_name = user_row . short_name if user_row . short_name . present?
2011-09-07 02:07:54 +08:00
end
2011-10-29 07:40:37 +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'
should_add_account_associations = false
should_update_account_associations = false
if ! status_is_active && ! user . new_record?
# if this user is deleted, we're just going to make sure the user isn't enrolled in anything in this root account and
# delete the pseudonym.
2016-08-20 04:18:15 +08:00
enrollment_ids = @root_account . enrollments . active . where ( user_id : user ) . where . not ( :workflow_state = > 'deleted' ) . pluck ( :id )
if enrollment_ids . any?
2016-11-20 12:10:24 +08:00
Enrollment . where ( id : enrollment_ids ) . update_all ( updated_at : Time . now . utc , workflow_state : 'deleted' )
EnrollmentState . where ( enrollment_id : enrollment_ids ) . update_all ( state : 'deleted' , state_is_current : true )
2016-08-20 04:18:15 +08:00
end
d = enrollment_ids . count
2016-11-20 12:10:24 +08:00
d += @root_account . all_group_memberships . active . where ( user_id : user ) . update_all ( updated_at : Time . now . utc , workflow_state : 'deleted' )
2016-07-12 03:47:38 +08:00
d += user . account_users . shard ( @root_account ) . where ( account_id : @root_account . all_accounts ) . delete_all
d += user . account_users . shard ( @root_account ) . where ( account_id : @root_account ) . delete_all
2015-10-09 02:51:57 +08:00
if 0 < d
2011-10-29 07:40:37 +08:00
should_update_account_associations = true
end
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
pseudo || = Pseudonym . new
2017-04-05 04:41:32 +08:00
pseudo . unique_id = user_row . login_id unless pseudo . stuck_sis_fields . include? ( :unique_id )
if user_row . authentication_provider_id . present?
unless @authentication_providers . key? ( user_row . authentication_provider_id )
2015-07-30 05:11:28 +08:00
begin
2017-04-05 04:41:32 +08:00
@authentication_providers [ user_row . authentication_provider_id ] =
@root_account . authentication_providers . active . find ( user_row . authentication_provider_id )
2015-07-30 05:11:28 +08:00
rescue ActiveRecord :: RecordNotFound
2017-04-05 04:41:32 +08:00
@authentication_providers [ user_row . authentication_provider_id ] = nil
2015-07-30 05:11:28 +08:00
end
end
2017-04-05 04:41:32 +08:00
unless ( pseudo . authentication_provider = @authentication_providers [ user_row . authentication_provider_id ] )
@messages << " unrecognized authentication provider #{ user_row . authentication_provider_id } for #{ user_row . user_id } , skipping "
2015-07-30 05:11:28 +08:00
next
end
else
pseudo . authentication_provider = nil
end
2017-04-05 04:41:32 +08:00
pseudo . sis_user_id = user_row . user_id
pseudo . integration_id = user_row . integration_id
2011-09-07 02:07:54 +08:00
pseudo . account = @root_account
2011-10-29 07:40:37 +08:00
pseudo . workflow_state = status_is_active ? 'active' : 'deleted'
if pseudo . new_record? && status_is_active
should_add_account_associations = true
elsif pseudo . workflow_state_changed?
if status_is_active
should_add_account_associations = true
else
should_update_account_associations = true
end
end
2011-09-07 02:07:54 +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
2017-04-05 04:41:32 +08:00
if ! user_row . password . blank? && ( pseudo . new_record? || pseudo . password_auto_generated && ! pseudo . valid_password? ( user_row . password ) )
pseudo . password = user_row . password
pseudo . password_confirmation = user_row . password
2011-09-07 02:07:54 +08:00
pseudo . password_auto_generated = true
end
2017-04-05 04:41:32 +08:00
pseudo . sis_ssha = user_row . ssha_password if ! user_row . ssha_password . blank?
2011-09-07 02:07:54 +08:00
pseudo . reset_persistence_token if pseudo . sis_ssha_changed? && pseudo . password_auto_generated
2012-11-17 14:49:00 +08:00
user_touched = false
2011-09-07 02:07:54 +08:00
begin
User . transaction ( :requires_new = > true ) do
if user . changed?
2012-11-17 14:49:00 +08:00
user_touched = true
2016-10-01 02:30:39 +08:00
if ! user . save && user . errors . size > 0
2017-04-05 04:41:32 +08:00
add_user_warning ( user . errors . first . join ( " " ) , user_row . user_id , user_row . login_id )
2016-10-01 02:30:39 +08:00
raise ImportError , user . errors . first . join ( " " )
end
2014-03-19 23:57:19 +08:00
elsif @batch
2011-09-07 02:07:54 +08:00
@users_to_set_sis_batch_ids << user . id
2011-05-13 03:58:47 +08:00
end
2011-09-07 02:07:54 +08:00
pseudo . user_id = user . id
if pseudo . changed?
2014-03-19 23:57:19 +08:00
pseudo . sis_batch_id = @batch . id if @batch
2016-10-01 02:30:39 +08:00
if ! pseudo . save_without_broadcasting && pseudo . errors . size > 0
2017-04-05 04:41:32 +08:00
add_user_warning ( pseudo . errors . first . join ( " " ) , user_row . user_id , user_row . login_id )
2016-10-01 02:30:39 +08:00
raise ImportError , pseudo . errors . first . join ( " " )
end
2011-09-07 02:07:54 +08:00
end
end
rescue = > e
2016-10-01 02:30:39 +08:00
Canvas :: Errors . capture_exception ( :sis_import , e )
2011-09-07 02:07:54 +08:00
next
end
2011-02-01 09:57:29 +08:00
2011-10-29 07:40:37 +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
2017-04-05 04:41:32 +08:00
if user_row . email . present? && EmailAddressValidator . valid? ( user_row . email )
refactor user creation/invitations closes #5833
fixes #5573, #5572, #5753
* communication channels are now only unique within a single user
* UserList changes
* Always resolve pseudonym#unique_ids
* Support looking up by SMS CCs
* Option to either require e-mails match an existing CC,
or e-mails that don't match a Pseudonym will always be
returned unattached (relying on better merging behavior
to not have a gazillion accounts created)
* Method to return users, creating new ones (*without* a
Pseudonym) if necessary. (can't create with a pseudonym,
since Pseudonym#unique_id is still unique, I can't have
multiple outstanding users with the same unique_id)
* EnrollmentsFromUserList is mostly gutted, now using UserList's
functionality directy.
* Use UserList for adding account admins, removing the now
unused Account#add_admin => User#find_by_email/User#assert_by_email
codepath
* Update UsersController#create to not worry about duplicate
communication channels
* Remove AccountsController#add_user, and just use
UsersController#create
* Change SIS::UserImporter to send out a merge opportunity
e-mail if a conflicting CC is found (but still create the CC)
* In /profile, don't worry about conflicting CCs (the CC confirmation
process will now allow merging)
* Remove CommunicationChannelsController#try_merge and #merge
* For the non-simple case of CoursesController#enrollment_invitation
redirect to /register (CommunicationsChannelController#confirm)
* Remove CoursesController#transfer_enrollment
* Move PseudonymsController#registration_confirmation to
CommunicationChannelsController#confirm (have to be able to
register an account without a Pseudonym yet)
* Fold the old direct confirm functionality in, if there are
no available merge opportunities
* Allow merging the new account with the currently logged in user
* Allow changing the Pseudonym#unique_id when registering a new
account (since there might be conflicts)
* Display a list of merge opportunities based on conflicting
communication channels
* Provide link(s) to log in as the other user,
redirecting back to the registration page after login is
complete (to complete the merge as the current user)
* Remove several assert_* methods that are no longer needed
* Update PseudonymSessionsController a bit to deal with the new
way of dealing with conflicting CCs (especially CCs from LDAP),
and to redirect back to the registration/confirmation page when
attempting to do a merge
* Expose the open_registration setting; use it to control if
inviting users to a course is able to create new users
Change-Id: If2f38818a71af656854d3bf8431ddbf5dcb84691
Reviewed-on: https://gerrit.instructure.com/6149
Tested-by: Hudson <hudson@instructure.com>
Reviewed-by: Jacob Fugal <jacob@instructure.com>
2011-10-13 04:30:48 +08:00
# 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
2011-12-31 07:10:04 +08:00
if status_is_active
2013-03-19 23:49:31 +08:00
ccs = CommunicationChannel . where ( " workflow_state='active' OR user_id=? " , user )
2011-12-31 07:10:04 +08:00
else
ccs = user . communication_channels
end
2017-04-05 04:41:32 +08:00
ccs = ccs . email . by_path ( user_row . email ) . to_a
refactor user creation/invitations closes #5833
fixes #5573, #5572, #5753
* communication channels are now only unique within a single user
* UserList changes
* Always resolve pseudonym#unique_ids
* Support looking up by SMS CCs
* Option to either require e-mails match an existing CC,
or e-mails that don't match a Pseudonym will always be
returned unattached (relying on better merging behavior
to not have a gazillion accounts created)
* Method to return users, creating new ones (*without* a
Pseudonym) if necessary. (can't create with a pseudonym,
since Pseudonym#unique_id is still unique, I can't have
multiple outstanding users with the same unique_id)
* EnrollmentsFromUserList is mostly gutted, now using UserList's
functionality directy.
* Use UserList for adding account admins, removing the now
unused Account#add_admin => User#find_by_email/User#assert_by_email
codepath
* Update UsersController#create to not worry about duplicate
communication channels
* Remove AccountsController#add_user, and just use
UsersController#create
* Change SIS::UserImporter to send out a merge opportunity
e-mail if a conflicting CC is found (but still create the CC)
* In /profile, don't worry about conflicting CCs (the CC confirmation
process will now allow merging)
* Remove CommunicationChannelsController#try_merge and #merge
* For the non-simple case of CoursesController#enrollment_invitation
redirect to /register (CommunicationsChannelController#confirm)
* Remove CoursesController#transfer_enrollment
* Move PseudonymsController#registration_confirmation to
CommunicationChannelsController#confirm (have to be able to
register an account without a Pseudonym yet)
* Fold the old direct confirm functionality in, if there are
no available merge opportunities
* Allow merging the new account with the currently logged in user
* Allow changing the Pseudonym#unique_id when registering a new
account (since there might be conflicts)
* Display a list of merge opportunities based on conflicting
communication channels
* Provide link(s) to log in as the other user,
redirecting back to the registration page after login is
complete (to complete the merge as the current user)
* Remove several assert_* methods that are no longer needed
* Update PseudonymSessionsController a bit to deal with the new
way of dealing with conflicting CCs (especially CCs from LDAP),
and to redirect back to the registration/confirmation page when
attempting to do a merge
* Expose the open_registration setting; use it to control if
inviting users to a course is able to create new users
Change-Id: If2f38818a71af656854d3bf8431ddbf5dcb84691
Reviewed-on: https://gerrit.instructure.com/6149
Tested-by: Hudson <hudson@instructure.com>
Reviewed-by: Jacob Fugal <jacob@instructure.com>
2011-10-13 04:30:48 +08:00
2012-01-04 07:40:03 +08:00
# 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
refactor user creation/invitations closes #5833
fixes #5573, #5572, #5753
* communication channels are now only unique within a single user
* UserList changes
* Always resolve pseudonym#unique_ids
* Support looking up by SMS CCs
* Option to either require e-mails match an existing CC,
or e-mails that don't match a Pseudonym will always be
returned unattached (relying on better merging behavior
to not have a gazillion accounts created)
* Method to return users, creating new ones (*without* a
Pseudonym) if necessary. (can't create with a pseudonym,
since Pseudonym#unique_id is still unique, I can't have
multiple outstanding users with the same unique_id)
* EnrollmentsFromUserList is mostly gutted, now using UserList's
functionality directy.
* Use UserList for adding account admins, removing the now
unused Account#add_admin => User#find_by_email/User#assert_by_email
codepath
* Update UsersController#create to not worry about duplicate
communication channels
* Remove AccountsController#add_user, and just use
UsersController#create
* Change SIS::UserImporter to send out a merge opportunity
e-mail if a conflicting CC is found (but still create the CC)
* In /profile, don't worry about conflicting CCs (the CC confirmation
process will now allow merging)
* Remove CommunicationChannelsController#try_merge and #merge
* For the non-simple case of CoursesController#enrollment_invitation
redirect to /register (CommunicationsChannelController#confirm)
* Remove CoursesController#transfer_enrollment
* Move PseudonymsController#registration_confirmation to
CommunicationChannelsController#confirm (have to be able to
register an account without a Pseudonym yet)
* Fold the old direct confirm functionality in, if there are
no available merge opportunities
* Allow merging the new account with the currently logged in user
* Allow changing the Pseudonym#unique_id when registering a new
account (since there might be conflicts)
* Display a list of merge opportunities based on conflicting
communication channels
* Provide link(s) to log in as the other user,
redirecting back to the registration page after login is
complete (to complete the merge as the current user)
* Remove several assert_* methods that are no longer needed
* Update PseudonymSessionsController a bit to deal with the new
way of dealing with conflicting CCs (especially CCs from LDAP),
and to redirect back to the registration/confirmation page when
attempting to do a merge
* Expose the open_registration setting; use it to control if
inviting users to a course is able to create new users
Change-Id: If2f38818a71af656854d3bf8431ddbf5dcb84691
Reviewed-on: https://gerrit.instructure.com/6149
Tested-by: Hudson <hudson@instructure.com>
Reviewed-by: Jacob Fugal <jacob@instructure.com>
2011-10-13 04:30:48 +08:00
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
2012-05-05 00:31:23 +08:00
# search for active/unconfirmed channels first, so we don't try to resurrect a conflicting cc
other_cc = ccs . find { | cc | cc . user_id == user . id && cc . id != sis_cc . try ( :id ) && ( cc . active? || cc . unconfirmed? ) }
other_cc || = ccs . find { | cc | cc . user_id == user . id && cc . id != sis_cc . try ( :id ) }
refactor user creation/invitations closes #5833
fixes #5573, #5572, #5753
* communication channels are now only unique within a single user
* UserList changes
* Always resolve pseudonym#unique_ids
* Support looking up by SMS CCs
* Option to either require e-mails match an existing CC,
or e-mails that don't match a Pseudonym will always be
returned unattached (relying on better merging behavior
to not have a gazillion accounts created)
* Method to return users, creating new ones (*without* a
Pseudonym) if necessary. (can't create with a pseudonym,
since Pseudonym#unique_id is still unique, I can't have
multiple outstanding users with the same unique_id)
* EnrollmentsFromUserList is mostly gutted, now using UserList's
functionality directy.
* Use UserList for adding account admins, removing the now
unused Account#add_admin => User#find_by_email/User#assert_by_email
codepath
* Update UsersController#create to not worry about duplicate
communication channels
* Remove AccountsController#add_user, and just use
UsersController#create
* Change SIS::UserImporter to send out a merge opportunity
e-mail if a conflicting CC is found (but still create the CC)
* In /profile, don't worry about conflicting CCs (the CC confirmation
process will now allow merging)
* Remove CommunicationChannelsController#try_merge and #merge
* For the non-simple case of CoursesController#enrollment_invitation
redirect to /register (CommunicationsChannelController#confirm)
* Remove CoursesController#transfer_enrollment
* Move PseudonymsController#registration_confirmation to
CommunicationChannelsController#confirm (have to be able to
register an account without a Pseudonym yet)
* Fold the old direct confirm functionality in, if there are
no available merge opportunities
* Allow merging the new account with the currently logged in user
* Allow changing the Pseudonym#unique_id when registering a new
account (since there might be conflicts)
* Display a list of merge opportunities based on conflicting
communication channels
* Provide link(s) to log in as the other user,
redirecting back to the registration page after login is
complete (to complete the merge as the current user)
* Remove several assert_* methods that are no longer needed
* Update PseudonymSessionsController a bit to deal with the new
way of dealing with conflicting CCs (especially CCs from LDAP),
and to redirect back to the registration/confirmation page when
attempting to do a merge
* Expose the open_registration setting; use it to control if
inviting users to a course is able to create new users
Change-Id: If2f38818a71af656854d3bf8431ddbf5dcb84691
Reviewed-on: https://gerrit.instructure.com/6149
Tested-by: Hudson <hudson@instructure.com>
Reviewed-by: Jacob Fugal <jacob@instructure.com>
2011-10-13 04:30:48 +08:00
# Handle the case where the SIS CC changes to match an already existing CC
if sis_cc && other_cc
sis_cc . destroy
sis_cc = nil
end
cc = sis_cc || other_cc || CommunicationChannel . new
cc . user_id = user . id
cc . pseudonym_id = pseudo . id
2017-04-05 04:41:32 +08:00
cc . path = user_row . email
2011-10-29 07:40:37 +08:00
cc . workflow_state = status_is_active ? 'active' : 'retired'
refactor user creation/invitations closes #5833
fixes #5573, #5572, #5753
* communication channels are now only unique within a single user
* UserList changes
* Always resolve pseudonym#unique_ids
* Support looking up by SMS CCs
* Option to either require e-mails match an existing CC,
or e-mails that don't match a Pseudonym will always be
returned unattached (relying on better merging behavior
to not have a gazillion accounts created)
* Method to return users, creating new ones (*without* a
Pseudonym) if necessary. (can't create with a pseudonym,
since Pseudonym#unique_id is still unique, I can't have
multiple outstanding users with the same unique_id)
* EnrollmentsFromUserList is mostly gutted, now using UserList's
functionality directy.
* Use UserList for adding account admins, removing the now
unused Account#add_admin => User#find_by_email/User#assert_by_email
codepath
* Update UsersController#create to not worry about duplicate
communication channels
* Remove AccountsController#add_user, and just use
UsersController#create
* Change SIS::UserImporter to send out a merge opportunity
e-mail if a conflicting CC is found (but still create the CC)
* In /profile, don't worry about conflicting CCs (the CC confirmation
process will now allow merging)
* Remove CommunicationChannelsController#try_merge and #merge
* For the non-simple case of CoursesController#enrollment_invitation
redirect to /register (CommunicationsChannelController#confirm)
* Remove CoursesController#transfer_enrollment
* Move PseudonymsController#registration_confirmation to
CommunicationChannelsController#confirm (have to be able to
register an account without a Pseudonym yet)
* Fold the old direct confirm functionality in, if there are
no available merge opportunities
* Allow merging the new account with the currently logged in user
* Allow changing the Pseudonym#unique_id when registering a new
account (since there might be conflicts)
* Display a list of merge opportunities based on conflicting
communication channels
* Provide link(s) to log in as the other user,
redirecting back to the registration page after login is
complete (to complete the merge as the current user)
* Remove several assert_* methods that are no longer needed
* Update PseudonymSessionsController a bit to deal with the new
way of dealing with conflicting CCs (especially CCs from LDAP),
and to redirect back to the registration/confirmation page when
attempting to do a merge
* Expose the open_registration setting; use it to control if
inviting users to a course is able to create new users
Change-Id: If2f38818a71af656854d3bf8431ddbf5dcb84691
Reviewed-on: https://gerrit.instructure.com/6149
Tested-by: Hudson <hudson@instructure.com>
Reviewed-by: Jacob Fugal <jacob@instructure.com>
2011-10-13 04:30:48 +08:00
newly_active = cc . path_changed? || ( cc . active? && cc . workflow_state_changed? )
2012-11-17 14:49:00 +08:00
if cc . changed?
2013-03-05 08:00:01 +08:00
if cc . valid?
cc . save_without_broadcasting
else
msg = " An email did not pass validation "
2017-04-05 04:41:32 +08:00
msg += " ( " + " #{ user_row . email } , error: "
2013-03-05 08:00:01 +08:00
msg += cc . errors . full_messages . join ( " , " ) + " ) "
raise ImportError , msg
end
2012-11-17 14:49:00 +08:00
user . touch unless user_touched
end
refactor user creation/invitations closes #5833
fixes #5573, #5572, #5753
* communication channels are now only unique within a single user
* UserList changes
* Always resolve pseudonym#unique_ids
* Support looking up by SMS CCs
* Option to either require e-mails match an existing CC,
or e-mails that don't match a Pseudonym will always be
returned unattached (relying on better merging behavior
to not have a gazillion accounts created)
* Method to return users, creating new ones (*without* a
Pseudonym) if necessary. (can't create with a pseudonym,
since Pseudonym#unique_id is still unique, I can't have
multiple outstanding users with the same unique_id)
* EnrollmentsFromUserList is mostly gutted, now using UserList's
functionality directy.
* Use UserList for adding account admins, removing the now
unused Account#add_admin => User#find_by_email/User#assert_by_email
codepath
* Update UsersController#create to not worry about duplicate
communication channels
* Remove AccountsController#add_user, and just use
UsersController#create
* Change SIS::UserImporter to send out a merge opportunity
e-mail if a conflicting CC is found (but still create the CC)
* In /profile, don't worry about conflicting CCs (the CC confirmation
process will now allow merging)
* Remove CommunicationChannelsController#try_merge and #merge
* For the non-simple case of CoursesController#enrollment_invitation
redirect to /register (CommunicationsChannelController#confirm)
* Remove CoursesController#transfer_enrollment
* Move PseudonymsController#registration_confirmation to
CommunicationChannelsController#confirm (have to be able to
register an account without a Pseudonym yet)
* Fold the old direct confirm functionality in, if there are
no available merge opportunities
* Allow merging the new account with the currently logged in user
* Allow changing the Pseudonym#unique_id when registering a new
account (since there might be conflicts)
* Display a list of merge opportunities based on conflicting
communication channels
* Provide link(s) to log in as the other user,
redirecting back to the registration page after login is
complete (to complete the merge as the current user)
* Remove several assert_* methods that are no longer needed
* Update PseudonymSessionsController a bit to deal with the new
way of dealing with conflicting CCs (especially CCs from LDAP),
and to redirect back to the registration/confirmation page when
attempting to do a merge
* Expose the open_registration setting; use it to control if
inviting users to a course is able to create new users
Change-Id: If2f38818a71af656854d3bf8431ddbf5dcb84691
Reviewed-on: https://gerrit.instructure.com/6149
Tested-by: Hudson <hudson@instructure.com>
Reviewed-by: Jacob Fugal <jacob@instructure.com>
2011-10-13 04:30:48 +08:00
pseudo . sis_communication_channel_id = pseudo . communication_channel_id = cc . id
if newly_active
2015-10-16 07:28:55 +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
other_ccs = ccs . reject { | other_cc |
cc_user_id = other_cc . user_id
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
}
refactor user creation/invitations closes #5833
fixes #5573, #5572, #5753
* communication channels are now only unique within a single user
* UserList changes
* Always resolve pseudonym#unique_ids
* Support looking up by SMS CCs
* Option to either require e-mails match an existing CC,
or e-mails that don't match a Pseudonym will always be
returned unattached (relying on better merging behavior
to not have a gazillion accounts created)
* Method to return users, creating new ones (*without* a
Pseudonym) if necessary. (can't create with a pseudonym,
since Pseudonym#unique_id is still unique, I can't have
multiple outstanding users with the same unique_id)
* EnrollmentsFromUserList is mostly gutted, now using UserList's
functionality directy.
* Use UserList for adding account admins, removing the now
unused Account#add_admin => User#find_by_email/User#assert_by_email
codepath
* Update UsersController#create to not worry about duplicate
communication channels
* Remove AccountsController#add_user, and just use
UsersController#create
* Change SIS::UserImporter to send out a merge opportunity
e-mail if a conflicting CC is found (but still create the CC)
* In /profile, don't worry about conflicting CCs (the CC confirmation
process will now allow merging)
* Remove CommunicationChannelsController#try_merge and #merge
* For the non-simple case of CoursesController#enrollment_invitation
redirect to /register (CommunicationsChannelController#confirm)
* Remove CoursesController#transfer_enrollment
* Move PseudonymsController#registration_confirmation to
CommunicationChannelsController#confirm (have to be able to
register an account without a Pseudonym yet)
* Fold the old direct confirm functionality in, if there are
no available merge opportunities
* Allow merging the new account with the currently logged in user
* Allow changing the Pseudonym#unique_id when registering a new
account (since there might be conflicts)
* Display a list of merge opportunities based on conflicting
communication channels
* Provide link(s) to log in as the other user,
redirecting back to the registration page after login is
complete (to complete the merge as the current user)
* Remove several assert_* methods that are no longer needed
* Update PseudonymSessionsController a bit to deal with the new
way of dealing with conflicting CCs (especially CCs from LDAP),
and to redirect back to the registration/confirmation page when
attempting to do a merge
* Expose the open_registration setting; use it to control if
inviting users to a course is able to create new users
Change-Id: If2f38818a71af656854d3bf8431ddbf5dcb84691
Reviewed-on: https://gerrit.instructure.com/6149
Tested-by: Hudson <hudson@instructure.com>
Reviewed-by: Jacob Fugal <jacob@instructure.com>
2011-10-13 04:30:48 +08:00
unless other_ccs . empty?
cc . send_merge_notification!
2011-05-13 03:58:47 +08:00
end
2011-09-07 02:07:54 +08:00
end
2017-04-05 04:41:32 +08:00
elsif user_row . email . present? && EmailAddressValidator . valid? ( user_row . email ) == false
@messages << " The email address associated with user ' #{ user_row . user_id } ' is invalid (email: ' #{ user_row . email } ') "
2011-09-07 02:07:54 +08:00
end
2011-02-01 09:57:29 +08:00
2011-09-07 02:07:54 +08:00
if pseudo . changed?
2017-04-05 09:22:02 +08:00
pseudo . sis_batch_id = user_row . sis_batch_id if user_row . sis_batch_id
2014-03-19 23:57:19 +08:00
pseudo . sis_batch_id = @batch . id if @batch
2013-03-05 08:00:01 +08:00
if pseudo . valid?
pseudo . save_without_broadcasting
else
msg = " A user did not pass validation "
2017-04-05 04:41:32 +08:00
msg += " ( " + " user: #{ user_row . user_id } , error: "
2013-03-05 08:00:01 +08:00
msg += pseudo . errors . full_messages . join ( " , " ) + " ) "
raise ImportError , msg
end
2014-03-19 23:57:19 +08:00
elsif @batch && pseudo . sis_batch_id != @batch . id
2011-09-07 02:07:54 +08:00
@pseudos_to_set_sis_batch_ids << pseudo . id
2011-04-30 04:03:25 +08:00
end
2015-10-15 22:27:40 +08:00
@success_count += 1
2011-09-07 02:07:54 +08:00
2011-04-29 04:52:57 +08:00
end
2011-02-01 09:57:29 +08:00
end
end
2016-01-28 09:13:56 +08:00
private
2016-10-01 02:30:39 +08:00
def add_user_warning ( message , user_id , login_id )
user_message = generate_readable_error_message (
message : message ,
user_id : user_id ,
login_id : login_id
)
@messages << user_message
end
2016-01-28 09:13:56 +08:00
ERRORS_TO_REASONS = {
'unique_id is invalid' = > " Invalid login_id: '%{login_id}' " ,
} . freeze
DEFAULT_REASON = 'Unknown reason: %{message}' . freeze
def generate_readable_error_message ( options )
response = ERRORS_TO_REASONS . fetch ( options [ :message ] ) { DEFAULT_REASON }
reason = format ( response , options )
result = " Could not save the user with user_id: ' #{ options [ :user_id ] } '. "
result << " #{ reason } "
result
end
2011-02-01 09:57:29 +08:00
end
end
2011-04-29 04:52:57 +08:00
end