318 lines
16 KiB
Ruby
318 lines
16 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
#
|
|
# Copyright (C) 2016 - present Instructure, Inc.
|
|
#
|
|
# 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/>.
|
|
#
|
|
|
|
require File.expand_path(File.dirname(__FILE__) + '/../sharding_spec_helper.rb')
|
|
|
|
describe UserListV2 do
|
|
before(:each) do
|
|
@account = Account.default
|
|
@account.settings = { :open_registration => true }
|
|
@account.save!
|
|
end
|
|
|
|
it "complains about invalid input" do
|
|
ul = UserListV2.new "i\x01nstructure", search_type: 'unique_id'
|
|
expect(ul.errors).to eq [{ :address => "i\x01nstructure", :details => :unparseable }]
|
|
end
|
|
|
|
it "responds responsibly to incorrect search type" do
|
|
expect { UserListV2.new "instructure", search_type: 'tarnation_declaration' }
|
|
.to raise_error(UserListV2::ParameterError)
|
|
end
|
|
|
|
it "does not fail with unicode names" do
|
|
ul = UserListV2.new '"senor molé" <blah@instructure.com>', search_type: 'unique_id'
|
|
expect(ul.missing_results.map { |x| [x[:user_name], x[:address]] }).to eq [["senor molé", "blah@instructure.com"]]
|
|
end
|
|
|
|
it "finds by SMS number" do
|
|
user_with_pseudonym(:name => "JT", :active_all => 1)
|
|
communication_channel(@user, { username: '8015555555@txt.att.net', path_type: 'sms', active_cc: true })
|
|
ul = UserListV2.new('(801) 555-5555', search_type: "cc_path")
|
|
expect(ul.resolved_results.first[:address]).to eq '(801) 555-5555'
|
|
expect(ul.resolved_results.first[:user_id]).to eq @user.id
|
|
expect(ul.resolved_results.first[:user_token]).to eq @user.token
|
|
|
|
ul = UserListV2.new('8015555555', search_type: "cc_path")
|
|
expect(ul.resolved_results.first[:address]).to eq '8015555555'
|
|
expect(ul.resolved_results.first[:user_id]).to eq @user.id
|
|
expect(ul.resolved_results.first[:user_token]).to eq @user.token
|
|
end
|
|
|
|
it "finds duplicates by SMS number" do
|
|
user_with_pseudonym(:name => "JT", :active_all => 1)
|
|
@user1 = @user
|
|
communication_channel(@user, { username: '8015555555@txt.att.net', path_type: 'sms', active_cc: true })
|
|
|
|
user_with_pseudonym(:name => "JT2", :active_all => 1)
|
|
communication_channel(@user, { username: '8015555555@txt.fakeplace.net', path_type: 'sms', active_cc: true })
|
|
|
|
ul = UserListV2.new('(801) 555-5555', search_type: "cc_path")
|
|
expect(ul.resolved_results).to be_empty
|
|
expect(ul.duplicate_results.first.map { |r| r[:user_id] }).to match_array([@user1.id, @user.id])
|
|
expect(ul.duplicate_results.first.map { |r| r[:user_token] }).to match_array([@user1.token, @user.token])
|
|
end
|
|
|
|
it "ignores unconfirmed emails" do
|
|
# maaaybe we want to preserve the old behavior with this... but whatevr ¯\_(ツ)_/¯
|
|
user_with_pseudonym(:name => 'JT', :username => 'jt@instructure.com', :active_all => true)
|
|
communication_channel(@user, { username: 'jt+2@instructure.com', active_cc: true })
|
|
@user1 = @user
|
|
user_with_pseudonym(:name => 'JT 1', :username => 'jt+1@instructure.com', :active_all => true)
|
|
communication_channel(@user, { username: 'jt+2@instructure.com' })
|
|
ul = UserListV2.new('jt+2@instructure.com', search_type: 'cc_path')
|
|
expect(ul.resolved_results.length).to eq 1
|
|
expect(ul.resolved_results.first[:user_id]).to eq @user1.id
|
|
expect(ul.duplicate_results).to be_empty
|
|
end
|
|
|
|
it "includes in duplicates if there is 1 active CC and 1 unconfirmed" do
|
|
Account.default.enable_feature!(:allow_unconfirmed_users_in_user_list)
|
|
# maaaybe we want to preserve the old behavior with this... but whatevr ¯\_(ツ)_/¯
|
|
user_with_pseudonym(:name => 'JT', :username => 'jt@instructure.com', :active_all => true)
|
|
communication_channel(@user, { username: 'jt+2@instructure.com', active_cc: true })
|
|
@user1 = @user
|
|
user_with_pseudonym(:name => 'JT 1', :username => 'jt+1@instructure.com', :active_all => true)
|
|
communication_channel(@user, { username: 'jt+2@instructure.com' })
|
|
ul = UserListV2.new('jt+2@instructure.com', search_type: 'cc_path')
|
|
expect(ul.resolved_results).to be_empty
|
|
expect(ul.duplicate_results.count).to eq 1
|
|
expect(ul.duplicate_results.first.map { |r| r[:user_id] }).to match_array([@user1.id, @user.id])
|
|
expect(ul.duplicate_results.first.map { |r| r[:user_token] }).to match_array([@user1.token, @user.token])
|
|
end
|
|
|
|
it "does not find users from untrusted accounts" do
|
|
account = Account.create!
|
|
user_with_pseudonym(:name => 'JT', :username => 'jt@instructure.com', :active_all => true, :account => account)
|
|
ul = UserListV2.new('jt@instructure.com', search_type: 'unique_id')
|
|
expect(ul.resolved_results).to be_empty
|
|
expect(ul.missing_results.first[:address]).to eq 'jt@instructure.com'
|
|
end
|
|
|
|
it "doesn't find site admins if you're not a site admin" do
|
|
user_with_pseudonym(:name => 'JT', :username => 'jt@instructure.com', :active_all => true, :account => Account.site_admin)
|
|
allow(Account.default).to receive(:trusted_account_ids).and_return([Account.site_admin.id])
|
|
jt = @user
|
|
user_with_pseudonym
|
|
other = @user
|
|
|
|
ul = UserListV2.new('jt@instructure.com', current_user: other, search_type: 'unique_id')
|
|
expect(ul.resolved_results).to be_empty
|
|
expect(ul.missing_results.first[:address]).to eq 'jt@instructure.com'
|
|
|
|
# when it's the user _from_ site admin doing it, it can be found
|
|
allow(Account.default).to receive(:trusted_account_ids).and_return([Account.site_admin.id])
|
|
ul = UserListV2.new('jt@instructure.com', current_user: jt, search_type: 'unique_id')
|
|
expect(ul.missing_results).to be_empty
|
|
expect(ul.resolved_results.first[:address]).to eq 'jt@instructure.com'
|
|
end
|
|
|
|
it "finds users from trusted accounts" do
|
|
account = Account.create!(:name => "naem")
|
|
allow(Account.default).to receive(:trusted_account_ids).and_return([account.id])
|
|
user_with_pseudonym(:name => 'JT', :username => 'jt@instructure.com', :active_all => true, :account => account)
|
|
ul = UserListV2.new('jt@instructure.com', :search_type => "unique_id")
|
|
expect(ul.resolved_results).to eq [{ :address => 'jt@instructure.com', :user_id => @user.id, :user_token => @user.token, :user_name => 'JT', :account_id => account.id, :account_name => account.name }]
|
|
end
|
|
|
|
it "shows duplicates for two results from the current account and the trusted account" do
|
|
user_with_pseudonym(:name => 'JT', :username => 'jt@instructure.com', :active_all => true)
|
|
@user1 = @user
|
|
account = Account.create!
|
|
allow(Account.default).to receive(:trusted_account_ids).and_return([account.id])
|
|
user_with_pseudonym(:name => 'JT', :username => 'jt@instructure.com', :active_all => true, :account => account)
|
|
ul = UserListV2.new('jt@instructure.com', :search_type => "unique_id")
|
|
|
|
expect(ul.resolved_results).to be_empty
|
|
expect(ul.duplicate_results.count).to eq 1
|
|
expect(ul.duplicate_results.first.map { |r| r[:user_id] }).to match_array([@user1.id, @user.id])
|
|
expect(ul.duplicate_results.first.map { |r| r[:user_token] }).to match_array([@user1.token, @user.token])
|
|
end
|
|
|
|
context 'when searching by sis id' do
|
|
it "raises an error without can_read_sis" do
|
|
expect {
|
|
UserListV2.new('SISID', root_account: @account, search_type: 'sis_user_id', can_read_sis: false)
|
|
}.to raise_error("cannot read sis ids")
|
|
end
|
|
|
|
it "shows duplicates for two results from the current account and the trusted account" do
|
|
account1 = Account.create!
|
|
account2 = Account.create!
|
|
allow(account1).to receive(:trusted_account_ids).and_return([account2.id])
|
|
allow(account1).to receive(:trust_exists?).and_return(true)
|
|
|
|
user_with_managed_pseudonym(:name => 'JT', :username => 'jt@instructure.com', :active_all => true, :account => account1, :sis_user_id => "SISID")
|
|
@user1 = @user
|
|
user_with_managed_pseudonym(:name => 'JT', :username => 'jt2@instructure.com', :active_all => true, :account => account2, :sis_user_id => "SISID")
|
|
ul = UserListV2.new('SISID', root_account: account1, search_type: 'sis_user_id', can_read_sis: true)
|
|
|
|
expect(ul.resolved_results).to be_empty
|
|
expect(ul.duplicate_results.count).to eq 1
|
|
dup = ul.duplicate_results.first
|
|
expect(dup.map { |r| r[:user_id] }).to match_array([@user1.id, @user.id])
|
|
expect(dup.map { |r| r[:user_token] }).to match_array([@user1.token, @user.token])
|
|
|
|
# should include additional idenfitying info on duplicates
|
|
expect(dup.map { |r| r[:email] }).to match_array(['jt@instructure.com', 'jt2@instructure.com'])
|
|
expect(dup.map { |r| r[:login_id] }).to match_array(['jt@instructure.com', 'jt2@instructure.com'])
|
|
expect(dup.map { |r| r[:sis_user_id] }).to match_array(['SISID', 'SISID'])
|
|
end
|
|
end
|
|
|
|
it "shows duplicates if there is a conflict of unique_ids from not-this-account" do
|
|
account1 = Account.create!
|
|
account2 = Account.create!
|
|
allow(Account.default).to receive(:trusted_account_ids).and_return([account1.id, account2.id])
|
|
|
|
user_with_pseudonym(:name => 'JT', :username => 'jt@instructure.com', :active_all => true, :account => account1)
|
|
allow_any_instantiation_of(@pseudonym).to receive(:works_for_account?).and_return(true)
|
|
user_with_pseudonym(:name => 'JT', :username => 'jt@instructure.com', :active_all => true, :account => account2)
|
|
allow_any_instantiation_of(@pseudonym).to receive(:works_for_account?).and_return(true)
|
|
ul = UserListV2.new('jt@instructure.com', search_type: 'unique_id')
|
|
expect(ul.resolved_results).to be_empty
|
|
expect(ul.duplicate_results.count).to eq 1
|
|
dups = ul.duplicate_results.first
|
|
expect(dups.map { |r| r[:account_id] }).to match_array([account1.id, account2.id])
|
|
expect(dups.map { |r| r[:login_id] }).to match_array(['jt@instructure.com', 'jt@instructure.com'])
|
|
dups.each do |h|
|
|
expect(h).to_not have_key(:sis_user_id) # only includes if can_read_sis is true
|
|
end
|
|
end
|
|
|
|
it "finds a user with multiple not-this-account pseudonyms" do
|
|
account1 = Account.create!
|
|
account2 = Account.create!
|
|
allow(Account.default).to receive(:trusted_account_ids).and_return([account1.id, account2.id])
|
|
user_with_pseudonym(:name => 'JT', :username => 'jt@instructure.com', :active_all => true, :account => account1)
|
|
@user.pseudonyms.create!(:unique_id => 'jt@instructure.com', :account => account2)
|
|
ul = UserListV2.new('jt@instructure.com', search_type: 'unique_id')
|
|
expect(ul.duplicate_results).to be_empty
|
|
expect(ul.resolved_results.count).to eq 1
|
|
expect(ul.resolved_results.first[:user_id]).to eq @user.id
|
|
expect(ul.resolved_results.first[:user_token]).to eq @user.token
|
|
end
|
|
|
|
it "does not find a user from a different account by SMS" do
|
|
account = Account.create!
|
|
user_with_pseudonym(:name => "JT", :active_all => 1, :account => account)
|
|
communication_channel(@user, { username: '8015555555@txt.att.net', path_type: 'sms', active_cc: true })
|
|
ul = UserListV2.new('(801) 555-5555', search_type: 'cc_path')
|
|
expect(ul.resolved_results).to eq []
|
|
expect(ul.missing_results.first[:address]).to eq '(801) 555-5555'
|
|
end
|
|
|
|
context "sharding" do
|
|
specs_require_sharding
|
|
|
|
it "finds a user from a trusted account in a different shard" do
|
|
@shard1.activate do
|
|
@account = Account.create!(:name => "accountnaem")
|
|
user_with_pseudonym(:name => 'JT', :username => 'jt@instructure.com', :active_all => true, :account => @account)
|
|
end
|
|
allow(Account.default).to receive(:trusted_account_ids).and_return([@account.id])
|
|
ul1 = UserListV2.new('jt@instructure.com', search_type: 'sis_user_id', can_read_sis: true)
|
|
expect(ul1.missing_results.map { |r| r[:address] }).to eq ['jt@instructure.com']
|
|
|
|
ul2 = UserListV2.new('jt@instructure.com', search_type: 'unique_id')
|
|
expect(ul2.resolved_results).to eq [{ :address => 'jt@instructure.com', :user_id => @user.id, :user_token => @user.token, :account_id => @account.id, :user_name => 'JT', :account_name => @account.name }]
|
|
end
|
|
|
|
it "does not get confused when dealing with cross-shard duplicate results that actually point to the same user" do
|
|
user_with_pseudonym(:name => 'JT', :username => 'jt@instructure.com', :active_all => true)
|
|
@shard1.activate do
|
|
@account = Account.create!(:name => "accountnaem")
|
|
ps = @account.pseudonyms.build(:user => @user, :unique_id => 'username', :password => 'password', :password_confirmation => 'password')
|
|
ps.save_without_session_maintenance
|
|
@user.communication_channels.first.update!(pseudonym: ps)
|
|
end
|
|
|
|
allow(Account.default).to receive(:trusted_account_ids).and_return([@account.id])
|
|
|
|
ul = UserListV2.new('jt@instructure.com', search_type: 'cc_path')
|
|
expect(ul.resolved_results.count).to eq 1
|
|
r = ul.resolved_results.first
|
|
expect(r[:user_id]).to eq @user.id
|
|
expect(r[:user_token]).to eq @user.token
|
|
expect(r[:account_id]).to eq Account.default.id
|
|
end
|
|
|
|
it "finds a user whose home shard is not the target shard" do
|
|
@shard1.activate do
|
|
@account = Account.create!(name: "non-local")
|
|
user_with_pseudonym(name: 'JT', username: 'jt@instructure.com', active_all: true, account: @account)
|
|
@pseudonym.destroy
|
|
end
|
|
Account.default.pseudonyms.create!(user: @user, unique_id: 'bob')
|
|
|
|
# strictly speaking we don't want this to be necessary,
|
|
# but I'm not ready to rely on globallookups exclusively
|
|
# for finding appropriate shards
|
|
allow(Account.default).to receive(:trusted_account_ids).and_return([@account.id])
|
|
|
|
ul = UserListV2.new('jt@instructure.com', search_type: 'cc_path')
|
|
expect(ul.resolved_results.count).to eq 1
|
|
r = ul.resolved_results.first
|
|
expect(r[:user_id]).to eq @user.id
|
|
expect(r[:user_token]).to eq @user.token
|
|
expect(r[:account_id]).to eq Account.default.id
|
|
end
|
|
|
|
context "global lookups" do
|
|
before do
|
|
@shard1.activate do
|
|
@account1 = Account.create!
|
|
@user1 = user_with_pseudonym(:name => 'JT', :username => 'jt@instructure.com', :active_all => true, :account => @account1)
|
|
end
|
|
@shard2.activate do
|
|
@account2 = Account.create!
|
|
@user2 = user_with_pseudonym(:name => 'JT', :username => 'jt@instructure.com', :active_all => true, :account => @account2)
|
|
end
|
|
|
|
allow(Account.default).to receive(:trusted_account_ids).and_return([Account.site_admin.id, @account1.id, @account2.id])
|
|
allow(GlobalLookups).to receive(:enabled?).and_return(true)
|
|
end
|
|
|
|
it "looks on every shard if there aren't that many shards to look on" do
|
|
# identifiable as `undefined method `associated_shards_for_column' for class `#<Class:0x00007f87ade80828>'`
|
|
skip_if_prepended_class_method_stubs_broken
|
|
Setting.set('global_lookups_shard_threshold', '3') # i.e. if we'd have to look on more than 3 shards, we should use global lookups
|
|
|
|
expect(Pseudonym).to receive(:associated_shards_for_column).never
|
|
ul = UserListV2.new('jt@instructure.com', search_type: 'unique_id')
|
|
expect(ul.resolved_results).to be_empty
|
|
expect(ul.duplicate_results.first.map { |r| r[:user_id] }).to match_array([@user1.id, @user2.id])
|
|
expect(ul.duplicate_results.first.map { |r| r[:user_token] }).to match_array([@user1.token, @user2.token])
|
|
end
|
|
|
|
it "uses the global lookups to restrict searched shard if there are enough shards to look on" do
|
|
skip_if_prepended_class_method_stubs_broken
|
|
Setting.set('global_lookups_shard_threshold', '1') # i.e. if we'd have to look on more than 1 shards, we should use global lookups
|
|
|
|
expect(Pseudonym).to receive(:associated_shards_for_column).once.with(:unique_id, 'jt@instructure.com').and_return([@shard1]) # don't look on shard2
|
|
ul = UserListV2.new('jt@instructure.com', search_type: 'unique_id')
|
|
expect(ul.duplicate_results).to be_empty
|
|
expect(ul.resolved_results.first[:user_id]).to eq @user1.id
|
|
expect(ul.resolved_results.first[:user_token]).to eq @user1.token
|
|
end
|
|
end
|
|
end
|
|
end
|