mirror of
https://github.com/discourse/discourse.git
synced 2024-11-25 10:20:58 -06:00
e120c94236
This commit fixes a regression introduced in 8979adc
where under certain conditions the groups syncing logic in Discourse Connect would try to add users to groups they're already members of and cause errors when users try to sign in using Discourse Connect.
1262 lines
37 KiB
Ruby
1262 lines
37 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
RSpec.describe DiscourseConnect do
|
|
before do
|
|
@discourse_connect_url = "http://example.com/discourse_sso"
|
|
@discourse_connect_secret = "shjkfdhsfkjh"
|
|
|
|
SiteSetting.discourse_connect_url = @discourse_connect_url
|
|
SiteSetting.enable_discourse_connect = true
|
|
SiteSetting.discourse_connect_secret = @discourse_connect_secret
|
|
SiteSetting.reserved_usernames = ''
|
|
Jobs.run_immediately!
|
|
end
|
|
|
|
def make_sso
|
|
sso = DiscourseConnectBase.new
|
|
sso.sso_url = "http://meta.discorse.org/topics/111"
|
|
sso.sso_secret = "supersecret"
|
|
sso.nonce = "testing"
|
|
sso.email = "some@email.com"
|
|
sso.username = "sam"
|
|
sso.name = "sam saffron"
|
|
sso.external_id = "100"
|
|
sso.avatar_url = "https://cdn.discourse.org/user_avatar.png"
|
|
sso.avatar_force_update = false
|
|
sso.bio = "about"
|
|
sso.admin = false
|
|
sso.moderator = false
|
|
sso.suppress_welcome_message = false
|
|
sso.require_activation = false
|
|
sso.title = "user title"
|
|
sso.custom_fields["a"] = "Aa"
|
|
sso.custom_fields["b.b"] = "B.b"
|
|
sso.website = "https://www.discourse.org/"
|
|
sso.location = "Home"
|
|
sso
|
|
end
|
|
|
|
def new_discourse_sso
|
|
DiscourseConnect.new(secure_session: secure_session)
|
|
end
|
|
|
|
def test_parsed(parsed, sso)
|
|
expect(parsed.nonce).to eq sso.nonce
|
|
expect(parsed.email).to eq sso.email
|
|
expect(parsed.username).to eq sso.username
|
|
expect(parsed.name).to eq sso.name
|
|
expect(parsed.external_id).to eq sso.external_id
|
|
expect(parsed.avatar_url).to eq sso.avatar_url
|
|
expect(parsed.avatar_force_update).to eq sso.avatar_force_update
|
|
expect(parsed.bio).to eq sso.bio
|
|
expect(parsed.admin).to eq sso.admin
|
|
expect(parsed.moderator).to eq sso.moderator
|
|
expect(parsed.suppress_welcome_message).to eq sso.suppress_welcome_message
|
|
expect(parsed.require_activation).to eq false
|
|
expect(parsed.title).to eq sso.title
|
|
expect(parsed.custom_fields["a"]).to eq "Aa"
|
|
expect(parsed.custom_fields["b.b"]).to eq "B.b"
|
|
expect(parsed.website).to eq sso.website
|
|
expect(parsed.location).to eq sso.location
|
|
end
|
|
|
|
it "can do round trip parsing correctly" do
|
|
sso = DiscourseConnectBase.new
|
|
sso.sso_secret = "test"
|
|
sso.name = "sam saffron"
|
|
sso.username = "sam"
|
|
sso.email = "sam@sam.com"
|
|
|
|
sso = DiscourseConnectBase.parse(sso.payload, "test")
|
|
|
|
expect(sso.name).to eq "sam saffron"
|
|
expect(sso.username).to eq "sam"
|
|
expect(sso.email).to eq "sam@sam.com"
|
|
end
|
|
|
|
let(:ip_address) { "127.0.0.1" }
|
|
let(:secure_session) { SecureSession.new("abc") }
|
|
|
|
it "bans bad external id" do
|
|
sso = new_discourse_sso
|
|
sso.username = "test"
|
|
sso.name = ""
|
|
sso.email = "test@test.com"
|
|
sso.suppress_welcome_message = true
|
|
|
|
sso.external_id = " "
|
|
|
|
expect do
|
|
sso.lookup_or_create_user(ip_address)
|
|
end.to raise_error(DiscourseConnect::BlankExternalId)
|
|
|
|
sso.external_id = nil
|
|
|
|
expect do
|
|
sso.lookup_or_create_user(ip_address)
|
|
end.to raise_error(DiscourseConnect::BlankExternalId)
|
|
|
|
# going for slight duplication here so our intent is crystal clear
|
|
%w{none nil Blank null}.each do |word|
|
|
sso.external_id = word
|
|
expect do
|
|
sso.lookup_or_create_user(ip_address)
|
|
end.to raise_error(DiscourseConnect::BannedExternalId)
|
|
end
|
|
end
|
|
|
|
it "can lookup or create user when name is blank" do
|
|
sso = new_discourse_sso
|
|
sso.username = "test"
|
|
sso.name = ""
|
|
sso.email = "test@test.com"
|
|
sso.external_id = "A"
|
|
sso.suppress_welcome_message = true
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
|
|
expect(user.persisted?).to eq(true)
|
|
end
|
|
|
|
it "unstaged users" do
|
|
SiteSetting.auth_overrides_name = true
|
|
|
|
email = "staged@user.com"
|
|
Fabricate(:user, staged: true, email: email)
|
|
|
|
sso = new_discourse_sso
|
|
sso.username = "staged"
|
|
sso.name = "Bob O'Bob"
|
|
sso.email = email
|
|
sso.external_id = "B"
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
|
|
user.reload
|
|
|
|
expect(user).to_not be_nil
|
|
expect(user.staged).to be(false)
|
|
|
|
expect(user.name).to eq("Bob O'Bob")
|
|
end
|
|
|
|
describe "reviewables" do
|
|
let(:sso) do
|
|
new_discourse_sso.tap do |sso|
|
|
sso.username = "staged"
|
|
sso.name = "Bob O'Bob"
|
|
sso.email = "bob@obob.com"
|
|
sso.external_id = "B"
|
|
end
|
|
end
|
|
|
|
it "doesn't create reviewables if we aren't approving users" do
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
reviewable = ReviewableUser.find_by(target: user)
|
|
expect(reviewable).to be_blank
|
|
end
|
|
|
|
it "creates reviewables if needed" do
|
|
SiteSetting.must_approve_users = true
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
reviewable = ReviewableUser.find_by(target: user)
|
|
expect(reviewable).to be_present
|
|
expect(reviewable).to be_pending
|
|
end
|
|
end
|
|
|
|
it "can set admin and moderator" do
|
|
admin_group = Group[:admins]
|
|
mod_group = Group[:moderators]
|
|
staff_group = Group[:staff]
|
|
|
|
sso = new_discourse_sso
|
|
sso.username = "misteradmin"
|
|
sso.name = "Bob Admin"
|
|
sso.email = "admin@admin.com"
|
|
sso.external_id = "id"
|
|
sso.admin = true
|
|
sso.moderator = true
|
|
sso.suppress_welcome_message = true
|
|
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
staff_group.reload
|
|
|
|
expect(mod_group.users.where('users.id = ?', user.id).exists?).to eq(true)
|
|
expect(staff_group.users.where('users.id = ?', user.id).exists?).to eq(true)
|
|
expect(admin_group.users.where('users.id = ?', user.id).exists?).to eq(true)
|
|
end
|
|
|
|
it "can force a list of groups with the groups attribute" do
|
|
user = Fabricate(:user)
|
|
group1 = Fabricate(:group, name: 'group1')
|
|
group2 = Fabricate(:group, name: 'group2')
|
|
|
|
sso = new_discourse_sso
|
|
sso.username = "bobsky"
|
|
sso.name = "Bob"
|
|
sso.email = user.email
|
|
sso.external_id = "A"
|
|
|
|
sso.groups = "#{group2.name.capitalize},group4,badname,trust_level_4"
|
|
sso.lookup_or_create_user(ip_address)
|
|
|
|
SiteSetting.discourse_connect_overrides_groups = true
|
|
|
|
group1.reload
|
|
expect(group1.usernames).to eq("")
|
|
expect(group2.usernames).to eq("")
|
|
|
|
group1.add(user)
|
|
group1.save!
|
|
|
|
sso.lookup_or_create_user(ip_address)
|
|
expect(group1.usernames).to eq("")
|
|
expect(group2.usernames).to eq(user.username)
|
|
|
|
sso.groups = "badname,trust_level_4"
|
|
sso.lookup_or_create_user(ip_address)
|
|
expect(group1.usernames).to eq("")
|
|
expect(group2.usernames).to eq("")
|
|
|
|
group1.add(user)
|
|
group1.save!
|
|
group2.add(user)
|
|
group2.save!
|
|
sso.groups = "#{group1.name},badname,trust_level_4"
|
|
|
|
sso.lookup_or_create_user(ip_address)
|
|
expect(group1.usernames).to eq(user.username)
|
|
expect(group2.usernames).to eq("")
|
|
end
|
|
|
|
it "can specify groups" do
|
|
|
|
user = Fabricate(:user)
|
|
|
|
add_group1 = Fabricate(:group, name: 'group1')
|
|
add_group2 = Fabricate(:group, name: 'group2')
|
|
existing_group = Fabricate(:group, name: 'group3')
|
|
add_group4 = Fabricate(:group, name: 'GROUP4')
|
|
existing_group2 = Fabricate(:group, name: 'GRoup5')
|
|
|
|
[existing_group, existing_group2].each do |g|
|
|
g.add(user)
|
|
g.save!
|
|
end
|
|
|
|
add_group1.add(user)
|
|
existing_group.save!
|
|
|
|
sso = new_discourse_sso
|
|
sso.username = "bobsky"
|
|
sso.name = "Bob"
|
|
sso.email = user.email
|
|
sso.external_id = "A"
|
|
|
|
sso.add_groups = "#{add_group1.name},#{add_group2.name.capitalize},group4,badname"
|
|
sso.remove_groups = "#{existing_group.name},#{existing_group2.name.downcase},badname"
|
|
|
|
sso.lookup_or_create_user(ip_address)
|
|
|
|
existing_group.reload
|
|
expect(existing_group.usernames).to eq("")
|
|
|
|
existing_group2.reload
|
|
expect(existing_group2.usernames).to eq("")
|
|
|
|
add_group1.reload
|
|
expect(add_group1.usernames).to eq(user.username)
|
|
|
|
add_group2.reload
|
|
expect(add_group2.usernames).to eq(user.username)
|
|
|
|
add_group4.reload
|
|
expect(add_group4.usernames).to eq(user.username)
|
|
end
|
|
|
|
it 'creates group logs when users are added to groups' do
|
|
user = Fabricate(:user)
|
|
group = Fabricate(:group, name: 'group1')
|
|
|
|
sso = new_discourse_sso
|
|
sso.username = "bobsky"
|
|
sso.name = "Bob"
|
|
sso.email = user.email
|
|
sso.external_id = "A"
|
|
sso.add_groups = "#{group.name},badname"
|
|
|
|
GroupHistory.destroy_all
|
|
|
|
sso.lookup_or_create_user(ip_address)
|
|
|
|
expect(group.reload.usernames).to eq(user.username)
|
|
expect(GroupHistory.exists?(
|
|
target_user_id: user.id,
|
|
acting_user: Discourse.system_user.id,
|
|
action: GroupHistory.actions[:add_user_to_group]
|
|
)).to eq(true)
|
|
end
|
|
|
|
it 'creates group logs when users are removed from groups' do
|
|
user = Fabricate(:user)
|
|
group = Fabricate(:group, name: 'group1')
|
|
group.add(user)
|
|
|
|
sso = new_discourse_sso
|
|
sso.username = "bobsky"
|
|
sso.name = "Bob"
|
|
sso.email = user.email
|
|
sso.external_id = "A"
|
|
sso.remove_groups = "#{group.name},badname"
|
|
|
|
GroupHistory.destroy_all
|
|
|
|
sso.lookup_or_create_user(ip_address)
|
|
|
|
expect(group.reload.usernames).to be_blank
|
|
expect(GroupHistory.exists?(
|
|
target_user_id: user.id,
|
|
acting_user: Discourse.system_user.id,
|
|
action: GroupHistory.actions[:remove_user_from_group]
|
|
)).to eq(true)
|
|
end
|
|
|
|
it 'creates group logs when users are added to groups when discourse_connect_overrides_groups setting is true' do
|
|
SiteSetting.discourse_connect_overrides_groups = true
|
|
|
|
user = Fabricate(:user)
|
|
group = Fabricate(:group, name: 'group1')
|
|
|
|
sso = new_discourse_sso
|
|
sso.username = "bobsky"
|
|
sso.name = "Bob"
|
|
sso.email = user.email
|
|
sso.external_id = "A"
|
|
sso.groups = "#{group.name},badname"
|
|
|
|
GroupHistory.destroy_all
|
|
|
|
sso.lookup_or_create_user(ip_address)
|
|
|
|
expect(group.reload.usernames).to eq(user.username)
|
|
expect(GroupHistory.exists?(
|
|
target_user_id: user.id,
|
|
acting_user: Discourse.system_user.id,
|
|
action: GroupHistory.actions[:add_user_to_group]
|
|
)).to eq(true)
|
|
end
|
|
|
|
it 'creates group logs when users are removed from groups when discourse_connect_overrides_groups setting is true' do
|
|
SiteSetting.discourse_connect_overrides_groups = true
|
|
|
|
user = Fabricate(:user)
|
|
group = Fabricate(:group, name: 'group1')
|
|
group.add(user)
|
|
|
|
sso = new_discourse_sso
|
|
sso.username = "bobsky"
|
|
sso.name = "Bob"
|
|
sso.email = user.email
|
|
sso.external_id = "A"
|
|
sso.groups = "badname"
|
|
|
|
GroupHistory.destroy_all
|
|
|
|
sso.lookup_or_create_user(ip_address)
|
|
|
|
expect(group.reload.usernames).to be_blank
|
|
expect(GroupHistory.exists?(
|
|
target_user_id: user.id,
|
|
acting_user: Discourse.system_user.id,
|
|
action: GroupHistory.actions[:remove_user_from_group]
|
|
)).to eq(true)
|
|
end
|
|
|
|
it 'behaves properly when auth_overrides_username is set but username is missing or blank' do
|
|
SiteSetting.auth_overrides_username = true
|
|
|
|
sso = new_discourse_sso
|
|
sso.username = "testuser"
|
|
sso.name = "test user"
|
|
sso.email = "test@test.com"
|
|
sso.external_id = "100"
|
|
sso.bio = "This **is** the bio"
|
|
sso.suppress_welcome_message = true
|
|
|
|
# create the original user
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.username).to eq "testuser"
|
|
|
|
# remove username from payload
|
|
sso.username = nil
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.username).to eq "testuser"
|
|
|
|
# set username in payload to blank
|
|
sso.username = ''
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.username).to eq "testuser"
|
|
end
|
|
|
|
it "can override name / email / username" do
|
|
admin = Fabricate(:admin)
|
|
|
|
SiteSetting.email_editable = false
|
|
SiteSetting.auth_overrides_name = true
|
|
SiteSetting.auth_overrides_email = true
|
|
SiteSetting.auth_overrides_username = true
|
|
|
|
sso = new_discourse_sso
|
|
sso.username = "bob%the$admin"
|
|
sso.name = "Bob Admin"
|
|
sso.email = admin.email
|
|
sso.external_id = "A"
|
|
|
|
sso.lookup_or_create_user(ip_address)
|
|
|
|
admin.reload
|
|
|
|
expect(admin.name).to eq "Bob Admin"
|
|
expect(admin.username).to eq "bob_the_admin"
|
|
expect(admin.email).to eq admin.email
|
|
|
|
sso.email = "TEST@bob.com"
|
|
|
|
sso.name = "Louis C.K."
|
|
|
|
sso.lookup_or_create_user(ip_address)
|
|
|
|
admin.reload
|
|
|
|
expect(admin.email).to eq("test@bob.com")
|
|
expect(admin.username).to eq "bob_the_admin"
|
|
expect(admin.name).to eq "Louis C.K."
|
|
end
|
|
|
|
it 'can override username properly when only the case changes' do
|
|
SiteSetting.auth_overrides_username = true
|
|
|
|
sso = new_discourse_sso
|
|
sso.username = "testuser"
|
|
sso.name = "test user"
|
|
sso.email = "test@test.com"
|
|
sso.external_id = "100"
|
|
sso.bio = "This **is** the bio"
|
|
sso.suppress_welcome_message = true
|
|
|
|
# create the original user
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.username).to eq "testuser"
|
|
|
|
# change the username case
|
|
sso.username = "TestUser"
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.username).to eq "TestUser"
|
|
end
|
|
|
|
it 'do not override username when a new username after fixing is the same' do
|
|
SiteSetting.auth_overrides_username = true
|
|
|
|
sso = new_discourse_sso
|
|
sso.username = "testuser"
|
|
sso.name = "test user"
|
|
sso.email = "test@test.com"
|
|
sso.external_id = "100"
|
|
|
|
# create the original user
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.username).to eq "testuser"
|
|
|
|
# change the username case
|
|
sso.username = "testuserგამარჯობა"
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.username).to eq "testuser"
|
|
end
|
|
|
|
it 'should preserve username when several users login with the same username' do
|
|
SiteSetting.auth_overrides_username = true
|
|
|
|
# if several users have username "bill" on the external site,
|
|
# they will have usernames bill, bill1, bill2 etc in Discourse:
|
|
Fabricate(:user, username: "bill")
|
|
Fabricate(:user, username: "bill1")
|
|
Fabricate(:user, username: "bill2")
|
|
Fabricate(:user, username: "bill4")
|
|
|
|
# the number should be preserved during subsequent logins
|
|
# bill3 should remain bill3
|
|
sso = new_discourse_sso
|
|
sso.username = "bill3"
|
|
sso.email = "test@test.com"
|
|
sso.external_id = "100"
|
|
sso.lookup_or_create_user(ip_address)
|
|
|
|
sso.username = "bill"
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
|
|
expect(user.username).to eq "bill3"
|
|
end
|
|
|
|
it "uses name if it's present in payload" do
|
|
sso = new_discourse_sso
|
|
sso.external_id = "100"
|
|
|
|
name = "John"
|
|
sso.name = name
|
|
sso.email = "mail@mail.com"
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
|
|
expect(user.name).to eq name
|
|
end
|
|
|
|
it "uses username for name suggestions if name isn't present in payload" do
|
|
sso = new_discourse_sso
|
|
sso.external_id = "100"
|
|
|
|
sso.name = ""
|
|
sso.username = "user_john"
|
|
sso.email = "mail@mail.com"
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
|
|
expect(user.name).to eq "User John"
|
|
end
|
|
|
|
it "uses username for username suggestions if it's present in payload" do
|
|
sso = new_discourse_sso
|
|
sso.external_id = "100"
|
|
|
|
username = "user_john"
|
|
sso.username = username
|
|
sso.email = "mail@mail.com"
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
|
|
expect(user.username).to eq username
|
|
end
|
|
|
|
it "uses name for username suggestions if username isn't present in payload" do
|
|
sso = new_discourse_sso
|
|
sso.external_id = "100"
|
|
|
|
sso.username = ""
|
|
sso.name = "John Smith"
|
|
sso.email = "mail@mail.com"
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
|
|
expect(user.username).to eq "John_Smith"
|
|
end
|
|
|
|
it "uses name for username suggestions if username consists entirely of disallowed characters" do
|
|
SiteSetting.unicode_usernames = false
|
|
|
|
sso = new_discourse_sso
|
|
sso.external_id = "100"
|
|
|
|
sso.username = "Πλάτων"
|
|
sso.name = "Plato"
|
|
sso.email = "mail@mail.com"
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
|
|
expect(user.username).to eq sso.name
|
|
end
|
|
|
|
it "stops using name as a source for username suggestions when disabled" do
|
|
SiteSetting.use_name_for_username_suggestions = false
|
|
|
|
sso = new_discourse_sso
|
|
sso.external_id = "100"
|
|
|
|
sso.username = nil
|
|
sso.name = "John Smith"
|
|
sso.email = "mail@mail.com"
|
|
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.username).to eq "user"
|
|
end
|
|
|
|
it "doesn't use email as a source for username suggestions by default" do
|
|
sso = new_discourse_sso
|
|
sso.external_id = "100"
|
|
|
|
# set username and name to nil, so they cannot be used as a source for suggestions
|
|
sso.username = nil
|
|
sso.name = nil
|
|
sso.email = "mail@mail.com"
|
|
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.username).to eq I18n.t('fallback_username')
|
|
end
|
|
|
|
it "uses email as a source for username suggestions if enabled" do
|
|
SiteSetting.use_email_for_username_and_name_suggestions = true
|
|
sso = new_discourse_sso
|
|
sso.external_id = "100"
|
|
|
|
# set username and name to nil, so they cannot be used as a source for suggestions
|
|
sso.username = nil
|
|
sso.name = nil
|
|
sso.email = "mail@mail.com"
|
|
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.username).to eq "mail"
|
|
end
|
|
|
|
it "doesn't use email as a source for name suggestions by default" do
|
|
sso = new_discourse_sso
|
|
sso.external_id = "100"
|
|
|
|
# set username and name to nil, so they cannot be used as a source for suggestions
|
|
sso.username = nil
|
|
sso.name = nil
|
|
sso.email = "mail@mail.com"
|
|
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.name).to eq ""
|
|
end
|
|
|
|
it "uses email as a source for name suggestions if enabled" do
|
|
SiteSetting.use_email_for_username_and_name_suggestions = true
|
|
sso = new_discourse_sso
|
|
sso.external_id = "100"
|
|
|
|
# set username and name to nil, so they cannot be used as a source for suggestions
|
|
sso.username = nil
|
|
sso.name = nil
|
|
sso.email = "mail@mail.com"
|
|
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.name).to eq "Mail"
|
|
end
|
|
|
|
it "uses email for username suggestions if username and name consist entirely of disallowed characters" do
|
|
SiteSetting.use_email_for_username_and_name_suggestions = true
|
|
SiteSetting.unicode_usernames = false
|
|
|
|
sso = new_discourse_sso
|
|
sso.external_id = "100"
|
|
|
|
sso.username = "Πλάτων"
|
|
sso.name = "Πλάτων"
|
|
sso.email = "mail@mail.com"
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
|
|
expect(user.username).to eq "mail"
|
|
end
|
|
|
|
it "can override username with a number at the end to a simpler username without a number" do
|
|
SiteSetting.auth_overrides_username = true
|
|
|
|
user = Fabricate(:user)
|
|
sso = new_discourse_sso
|
|
sso.external_id = "A"
|
|
sso.email = user.email
|
|
|
|
username_with_number = "bob1"
|
|
username_without_number = "bob"
|
|
|
|
sso.username = username_with_number
|
|
sso.lookup_or_create_user(ip_address)
|
|
user.reload
|
|
expect(user.username).to eq username_with_number
|
|
|
|
sso.username = username_without_number
|
|
sso.lookup_or_create_user(ip_address)
|
|
user.reload
|
|
expect(user.username).to eq username_without_number
|
|
end
|
|
|
|
it "can override username after min_username_length was made smaller" do
|
|
SiteSetting.auth_overrides_username = true
|
|
|
|
user = Fabricate(:user)
|
|
sso = new_discourse_sso
|
|
sso.external_id = "A"
|
|
sso.email = user.email
|
|
|
|
long_username = "bob"
|
|
short_username = "bo"
|
|
|
|
SiteSetting.min_username_length = 3
|
|
sso.username = long_username
|
|
sso.lookup_or_create_user(ip_address)
|
|
user.reload
|
|
expect(user.username).to eq long_username
|
|
|
|
SiteSetting.min_username_length = 2
|
|
sso.username = short_username
|
|
sso.lookup_or_create_user(ip_address)
|
|
user.reload
|
|
expect(user.username).to eq short_username
|
|
end
|
|
|
|
it "can fill in data on way back" do
|
|
sso = make_sso
|
|
|
|
url, payload = sso.to_url.split("?")
|
|
expect(url).to eq sso.sso_url
|
|
parsed = DiscourseConnectBase.parse(payload, "supersecret")
|
|
|
|
test_parsed(parsed, sso)
|
|
end
|
|
|
|
it "handles sso_url with query params" do
|
|
sso = make_sso
|
|
sso.sso_url = "http://tcdev7.wpengine.com/?action=showlogin"
|
|
|
|
expect(sso.to_url.split('?').size).to eq 2
|
|
|
|
url, payload = sso.to_url.split("?")
|
|
expect(url).to eq "http://tcdev7.wpengine.com/"
|
|
parsed = DiscourseConnectBase.parse(payload, "supersecret")
|
|
|
|
test_parsed(parsed, sso)
|
|
end
|
|
|
|
it "validates nonce" do
|
|
_ , payload = DiscourseConnect.generate_url(secure_session: secure_session).split("?")
|
|
|
|
sso = DiscourseConnect.parse(payload, secure_session: secure_session)
|
|
expect(sso.nonce_valid?).to eq true
|
|
|
|
other_session_sso = DiscourseConnect.parse(payload, secure_session: SecureSession.new("differentsession"))
|
|
expect(other_session_sso.nonce_valid?).to eq false
|
|
|
|
sso.expire_nonce!
|
|
|
|
expect(sso.nonce_valid?).to eq false
|
|
end
|
|
|
|
it "allows disabling CSRF protection" do
|
|
SiteSetting.discourse_connect_csrf_protection = false
|
|
_ , payload = DiscourseConnect.generate_url(secure_session: secure_session).split("?")
|
|
|
|
sso = DiscourseConnect.parse(payload, secure_session: secure_session)
|
|
expect(sso.nonce_valid?).to eq true
|
|
|
|
other_session_sso = DiscourseConnect.parse(payload, secure_session: SecureSession.new("differentsession"))
|
|
expect(other_session_sso.nonce_valid?).to eq true
|
|
|
|
sso.expire_nonce!
|
|
|
|
expect(sso.nonce_valid?).to eq false
|
|
end
|
|
|
|
it "generates a correct sso url" do
|
|
url, payload = DiscourseConnect.generate_url(secure_session: secure_session).split("?")
|
|
expect(url).to eq @discourse_connect_url
|
|
|
|
sso = DiscourseConnect.parse(payload, secure_session: secure_session)
|
|
expect(sso.nonce).to_not be_nil
|
|
end
|
|
|
|
describe 'nonce error' do
|
|
it "generates correct error message when nonce has already been used" do
|
|
_ , payload = DiscourseConnect.generate_url(secure_session: secure_session).split("?")
|
|
|
|
sso = DiscourseConnect.parse(payload, secure_session: secure_session)
|
|
expect(sso.nonce_valid?).to eq true
|
|
|
|
sso.expire_nonce!
|
|
expect(sso.nonce_error).to eq("Nonce has already been used")
|
|
end
|
|
|
|
it "generates correct error message when nonce is expired" do
|
|
_ , payload = DiscourseConnect.generate_url(secure_session: secure_session).split("?")
|
|
|
|
sso = DiscourseConnect.parse(payload, secure_session: secure_session)
|
|
expect(sso.nonce_valid?).to eq true
|
|
|
|
Discourse.cache.delete(sso.used_nonce_key)
|
|
expect(sso.nonce_error).to eq("Nonce is incorrect, was generated in a different browser session, or has expired")
|
|
end
|
|
|
|
it "generates correct error message when nonce is expired, and csrf protection disabled" do
|
|
SiteSetting.discourse_connect_csrf_protection = false
|
|
_ , payload = DiscourseConnect.generate_url(secure_session: secure_session).split("?")
|
|
|
|
sso = DiscourseConnect.parse(payload, secure_session: secure_session)
|
|
expect(sso.nonce_valid?).to eq true
|
|
|
|
Discourse.cache.delete(sso.used_nonce_key)
|
|
expect(sso.nonce_error).to eq("Nonce is incorrect, or has expired")
|
|
end
|
|
end
|
|
|
|
describe 'user locale' do
|
|
it 'sets default user locale if specified' do
|
|
SiteSetting.allow_user_locale = true
|
|
|
|
sso = new_discourse_sso
|
|
sso.username = "test"
|
|
sso.name = "test"
|
|
sso.email = "test@test.com"
|
|
sso.external_id = "123"
|
|
sso.locale = "es"
|
|
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
|
|
expect(user.locale).to eq("es")
|
|
|
|
user.update_column(:locale, "he")
|
|
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.locale).to eq("he")
|
|
|
|
sso.locale_force_update = true
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.locale).to eq("es")
|
|
|
|
sso.locale = "fake"
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.locale).to eq("es")
|
|
end
|
|
end
|
|
|
|
describe 'trusting emails' do
|
|
let(:sso) do
|
|
sso = new_discourse_sso
|
|
sso.username = "test"
|
|
sso.name = "test"
|
|
sso.email = "test@example.com"
|
|
sso.external_id = "A"
|
|
sso.suppress_welcome_message = true
|
|
sso
|
|
end
|
|
|
|
it 'activates users by default' do
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.active).to eq(true)
|
|
end
|
|
|
|
it 'does not activate user when asked not to' do
|
|
sso.require_activation = true
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.active).to eq(false)
|
|
|
|
user.activate
|
|
|
|
sso.external_id = "B"
|
|
|
|
expect do
|
|
sso.lookup_or_create_user(ip_address)
|
|
end.to raise_error(ActiveRecord::RecordInvalid)
|
|
|
|
end
|
|
|
|
it 'does not deactivate user if email provided is capitalized' do
|
|
SiteSetting.email_editable = false
|
|
SiteSetting.auth_overrides_email = true
|
|
sso.require_activation = true
|
|
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.active).to eq(false)
|
|
|
|
user.update_columns(active: true)
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.active).to eq(true)
|
|
|
|
sso.email = "Test@example.com"
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.active).to eq(true)
|
|
end
|
|
|
|
it 'deactivates accounts that have updated email address' do
|
|
|
|
SiteSetting.email_editable = false
|
|
SiteSetting.auth_overrides_email = true
|
|
sso.require_activation = true
|
|
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.active).to eq(false)
|
|
|
|
old_email = user.email
|
|
|
|
user.update_columns(active: true)
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.active).to eq(true)
|
|
|
|
user.primary_email.update_columns(email: 'xXx@themovie.com')
|
|
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.email).to eq(old_email)
|
|
expect(user.active).to eq(false)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
describe 'welcome emails' do
|
|
let(:sso) {
|
|
sso = new_discourse_sso
|
|
sso.username = "test"
|
|
sso.name = "test"
|
|
sso.email = "test@example.com"
|
|
sso.external_id = "A"
|
|
sso
|
|
}
|
|
|
|
it "sends a welcome email by default" do
|
|
User.any_instance.expects(:enqueue_welcome_message).once
|
|
_user = sso.lookup_or_create_user(ip_address)
|
|
end
|
|
|
|
it "suppresses the welcome email when asked to" do
|
|
User.any_instance.expects(:enqueue_welcome_message).never
|
|
sso.suppress_welcome_message = true
|
|
_user = sso.lookup_or_create_user(ip_address)
|
|
end
|
|
end
|
|
|
|
describe 'setting title for a user' do
|
|
let(:sso) {
|
|
sso = new_discourse_sso
|
|
sso.username = 'test'
|
|
sso.name = 'test'
|
|
sso.email = 'test@test.com'
|
|
sso.external_id = '100'
|
|
sso.title = "The User's Title"
|
|
sso
|
|
}
|
|
|
|
it 'sets title correctly' do
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.title).to eq(sso.title)
|
|
|
|
sso.title = "farmer"
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
|
|
expect(user.title).to eq("farmer")
|
|
|
|
sso.title = nil
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
|
|
expect(user.title).to eq("farmer")
|
|
end
|
|
end
|
|
|
|
describe 'setting bio for a user' do
|
|
let(:sso) do
|
|
sso = new_discourse_sso
|
|
sso.username = "test"
|
|
sso.name = "test"
|
|
sso.email = "test@test.com"
|
|
sso.external_id = "100"
|
|
sso.bio = "This **is** the bio"
|
|
sso.suppress_welcome_message = true
|
|
sso
|
|
end
|
|
|
|
it 'can set bio if supplied on new users or users with empty bio' do
|
|
# new account
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.user_profile.bio_cooked).to match_html("<p>This <strong>is</strong> the bio</p>")
|
|
|
|
# no override by default
|
|
sso.bio = "new profile"
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
|
|
expect(user.user_profile.bio_cooked).to match_html("<p>This <strong>is</strong> the bio</p>")
|
|
|
|
# yes override for blank
|
|
user.user_profile.update!(bio_raw: '')
|
|
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.user_profile.bio_cooked).to match_html("<p>new profile</p>")
|
|
|
|
# yes override if site setting
|
|
sso.bio = "new profile 2"
|
|
SiteSetting.discourse_connect_overrides_bio = true
|
|
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
expect(user.user_profile.bio_cooked).to match_html("<p>new profile 2</p")
|
|
end
|
|
|
|
end
|
|
|
|
context 'when discourse_connect_overrides_avatar is not enabled' do
|
|
|
|
it "correctly handles provided avatar_urls" do
|
|
sso = new_discourse_sso
|
|
sso.external_id = 666
|
|
sso.email = "sam@sam.com"
|
|
sso.name = "sam"
|
|
sso.username = "sam"
|
|
sso.avatar_url = "http://awesome.com/image.png"
|
|
sso.suppress_welcome_message = true
|
|
|
|
FileHelper.stubs(:download).returns(file_from_fixtures("logo.png"), file_from_fixtures("logo.png"))
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
user.reload
|
|
avatar_id = user.uploaded_avatar_id
|
|
|
|
# initial creation ...
|
|
expect(avatar_id).to_not eq(nil)
|
|
|
|
# junk avatar id should be updated
|
|
old_id = user.uploaded_avatar_id
|
|
Upload.destroy(old_id)
|
|
FileHelper.stubs(:download).returns(file_from_fixtures("logo.png"), file_from_fixtures("logo.png"))
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
user.reload
|
|
avatar_id = user.uploaded_avatar_id
|
|
|
|
expect(avatar_id).to_not eq(nil)
|
|
expect(old_id).to_not eq(avatar_id)
|
|
|
|
# FileHelper.stubs(:download) { raise "should not be called" }
|
|
# sso.avatar_url = "https://some.new/avatar.png"
|
|
# user = sso.lookup_or_create_user(ip_address)
|
|
# user.reload
|
|
#
|
|
# # avatar updated but no override specified ...
|
|
# expect(user.uploaded_avatar_id).to eq(avatar_id)
|
|
#
|
|
# sso.avatar_force_update = true
|
|
# FileHelper.stubs(:download).returns(file_from_fixtures("logo-dev.png"))
|
|
# user = sso.lookup_or_create_user(ip_address)
|
|
# user.reload
|
|
#
|
|
# # we better have a new avatar
|
|
# expect(user.uploaded_avatar_id).not_to eq(avatar_id)
|
|
# expect(user.uploaded_avatar_id).not_to eq(nil)
|
|
#
|
|
# avatar_id = user.uploaded_avatar_id
|
|
#
|
|
# sso.avatar_force_update = true
|
|
# FileHelper.stubs(:download) { raise "not found" }
|
|
# user = sso.lookup_or_create_user(ip_address)
|
|
# user.reload
|
|
#
|
|
# # we better have the same avatar
|
|
# expect(user.uploaded_avatar_id).to eq(avatar_id)
|
|
end
|
|
|
|
end
|
|
|
|
context 'when discourse_connect_overrides_avatar is enabled' do
|
|
fab!(:sso_record) { Fabricate(:single_sign_on_record, external_avatar_url: "http://example.com/an_image.png") }
|
|
|
|
let!(:sso) {
|
|
sso = new_discourse_sso
|
|
sso.username = "test"
|
|
sso.name = "test"
|
|
sso.email = sso_record.user.email
|
|
sso.external_id = sso_record.external_id
|
|
sso
|
|
}
|
|
|
|
let(:logo) { file_from_fixtures("logo.png") }
|
|
|
|
before do
|
|
SiteSetting.discourse_connect_overrides_avatar = true
|
|
end
|
|
|
|
it "deal with no avatar url passed for an existing user with an avatar" do
|
|
Sidekiq::Testing.inline! do
|
|
# Deliberately not setting avatar_url so it should not update
|
|
sso_record.user.update_columns(uploaded_avatar_id: -1)
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
user.reload
|
|
|
|
expect(user).to_not be_nil
|
|
expect(user.uploaded_avatar_id).to eq(-1)
|
|
end
|
|
end
|
|
|
|
it "deal with no avatar_force_update passed as a boolean" do
|
|
Sidekiq::Testing.inline! do
|
|
FileHelper.stubs(:download).returns(logo)
|
|
|
|
sso_record.user.update_columns(uploaded_avatar_id: -1)
|
|
|
|
sso.avatar_url = "http://example.com/a_different_image.png"
|
|
sso.avatar_force_update = false
|
|
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
user.reload
|
|
|
|
expect(user).to_not be_nil
|
|
expect(user.uploaded_avatar_id).to_not eq(-1)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when discourse_connect_overrides_profile_background is not enabled' do
|
|
|
|
it "correctly handles provided profile_background_urls" do
|
|
sso = new_discourse_sso
|
|
sso.external_id = 666
|
|
sso.email = "sam@sam.com"
|
|
sso.name = "sam"
|
|
sso.username = "sam"
|
|
sso.profile_background_url = "http://awesome.com/image.png"
|
|
sso.suppress_welcome_message = true
|
|
|
|
FileHelper.stubs(:download).returns(file_from_fixtures("logo.png"))
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
user.reload
|
|
user.user_profile.reload
|
|
profile_background_url = user.profile_background_upload.url
|
|
|
|
# initial creation ...
|
|
expect(profile_background_url).to_not eq(nil)
|
|
expect(profile_background_url).to_not eq('')
|
|
|
|
FileHelper.stubs(:download) { raise "should not be called" }
|
|
sso.profile_background_url = "https://some.new/avatar.png"
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
user.reload
|
|
user.user_profile.reload
|
|
|
|
# profile_background updated but no override specified ...
|
|
expect(user.profile_background_upload.url).to eq(profile_background_url)
|
|
end
|
|
end
|
|
|
|
context 'when discourse_connect_overrides_profile_background is enabled' do
|
|
fab!(:sso_record) { Fabricate(:single_sign_on_record, external_profile_background_url: "http://example.com/an_image.png") }
|
|
|
|
let!(:sso) {
|
|
sso = new_discourse_sso
|
|
sso.username = "test"
|
|
sso.name = "test"
|
|
sso.email = sso_record.user.email
|
|
sso.external_id = sso_record.external_id
|
|
sso
|
|
}
|
|
|
|
let(:logo) { file_from_fixtures("logo.png") }
|
|
|
|
before do
|
|
SiteSetting.discourse_connect_overrides_profile_background = true
|
|
end
|
|
|
|
it "deal with no profile_background_url passed for an existing user with a profile_background" do
|
|
# Deliberately not setting profile_background_url so it should not update
|
|
sso_record.user.user_profile.clear_profile_background
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
user.reload
|
|
|
|
expect(user.profile_background_upload).to eq(nil)
|
|
end
|
|
|
|
it "deal with a profile_background_url passed for an existing user with a profile_background" do
|
|
url = "http://example.com/a_different_image.png"
|
|
stub_request(:get, url).to_return(body: logo)
|
|
|
|
sso_record.user.user_profile.clear_profile_background
|
|
sso.profile_background_url = "http://example.com/a_different_image.png"
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
user.reload
|
|
|
|
expect(user.profile_background_upload).to_not eq(nil)
|
|
end
|
|
end
|
|
|
|
context 'when discourse_connect_overrides_card_background is not enabled' do
|
|
|
|
it "correctly handles provided card_background_urls" do
|
|
sso = new_discourse_sso
|
|
sso.external_id = 666
|
|
sso.email = "sam@sam.com"
|
|
sso.name = "sam"
|
|
sso.username = "sam"
|
|
sso.card_background_url = "http://awesome.com/image.png"
|
|
sso.suppress_welcome_message = true
|
|
FileHelper.stubs(:download).returns(file_from_fixtures("logo.png"))
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
user.reload
|
|
user.user_profile.reload
|
|
card_background_url = user.user_profile.card_background_upload.url
|
|
|
|
# initial creation ...
|
|
expect(card_background_url).to be_present
|
|
|
|
FileHelper.stubs(:download) { raise "should not be called" }
|
|
sso.card_background_url = "https://some.new/avatar.png"
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
user.reload
|
|
user.user_profile.reload
|
|
|
|
# card_background updated but no override specified ...
|
|
expect(user.user_profile.card_background_upload.url).to eq(
|
|
card_background_url
|
|
)
|
|
end
|
|
end
|
|
|
|
context 'when discourse_connect_overrides_card_background is enabled' do
|
|
fab!(:sso_record) { Fabricate(:single_sign_on_record, external_card_background_url: "http://example.com/an_image.png") }
|
|
|
|
let!(:sso) {
|
|
sso = new_discourse_sso
|
|
sso.username = "test"
|
|
sso.name = "test"
|
|
sso.email = sso_record.user.email
|
|
sso.external_id = sso_record.external_id
|
|
sso
|
|
}
|
|
|
|
let(:logo) { file_from_fixtures("logo.png") }
|
|
|
|
before do
|
|
SiteSetting.discourse_connect_overrides_card_background = true
|
|
end
|
|
|
|
it "deal with no card_background_url passed for an existing user with a card_background" do
|
|
# Deliberately not setting card_background_url so it should not update
|
|
sso_record.user.user_profile.clear_card_background
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
user.reload
|
|
|
|
expect(user.user_profile.card_background_upload).to eq(nil)
|
|
end
|
|
|
|
it "deal with a card_background_url passed for an existing user with a card_background_url" do
|
|
url = "http://example.com/a_different_image.png"
|
|
stub_request(:get, url).to_return(body: logo)
|
|
|
|
sso_record.user.user_profile.clear_card_background
|
|
sso.card_background_url = url
|
|
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
user.reload
|
|
|
|
expect(user.user_profile.card_background_upload.url).to_not eq('')
|
|
end
|
|
end
|
|
|
|
context "when user is staged" do
|
|
it "uses username of the staged user if username is not present in payload" do
|
|
staged_username = "staged_user"
|
|
email = "staged@user.com"
|
|
Fabricate(:user, staged: true, username: staged_username, email: email)
|
|
|
|
sso = new_discourse_sso
|
|
sso.email = email
|
|
sso.external_id = "B"
|
|
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
user.reload
|
|
|
|
expect(user.username).to eq(staged_username)
|
|
end
|
|
|
|
it "uses username of the staged user if username in payload is the same" do
|
|
# it's important to check this, because we had regressions
|
|
# where usernames were changed to the same username with "1" added at the end
|
|
staged_username = "staged_user"
|
|
email = "staged@user.com"
|
|
Fabricate(:user, staged: true, username: staged_username, email: email)
|
|
|
|
sso = new_discourse_sso
|
|
sso.username = staged_username
|
|
sso.email = email
|
|
sso.external_id = "B"
|
|
|
|
user = sso.lookup_or_create_user(ip_address)
|
|
user.reload
|
|
|
|
expect(user.username).to eq(staged_username)
|
|
end
|
|
end
|
|
end
|