mirror of
https://github.com/discourse/discourse.git
synced 2025-02-11 05:44:59 +00:00
In some cases add_groups and remove_groups is too much work, some sites may wish to simply synchronize group membership based on a list. When sso_overrides_groups is on all not automatic group membership is sourced from SSO. Note if you omit to specify groups, they will be cleared out.
583 lines
16 KiB
Ruby
583 lines
16 KiB
Ruby
require "rails_helper"
|
|
|
|
describe DiscourseSingleSignOn do
|
|
before do
|
|
@sso_url = "http://somesite.com/discourse_sso"
|
|
@sso_secret = "shjkfdhsfkjh"
|
|
|
|
SiteSetting.sso_url = @sso_url
|
|
SiteSetting.enable_sso = true
|
|
SiteSetting.sso_secret = @sso_secret
|
|
end
|
|
|
|
def make_sso
|
|
sso = SingleSignOn.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
|
|
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"
|
|
end
|
|
|
|
it "can do round trip parsing correctly" do
|
|
sso = SingleSignOn.new
|
|
sso.sso_secret = "test"
|
|
sso.name = "sam saffron"
|
|
sso.username = "sam"
|
|
sso.email = "sam@sam.com"
|
|
|
|
sso = SingleSignOn.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" }
|
|
|
|
it "can lookup or create user when name is blank" do
|
|
sso = DiscourseSingleSignOn.new
|
|
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.sso_overrides_name = true
|
|
|
|
email = "staged@user.com"
|
|
Fabricate(:user, staged: true, email: email)
|
|
|
|
sso = DiscourseSingleSignOn.new
|
|
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
|
|
|
|
it "can set admin and moderator" do
|
|
admin_group = Group[:admins]
|
|
mod_group = Group[:moderators]
|
|
staff_group = Group[:staff]
|
|
|
|
sso = DiscourseSingleSignOn.new
|
|
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 = DiscourseSingleSignOn.new
|
|
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.sso_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)
|
|
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 = DiscourseSingleSignOn.new
|
|
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 'can override username properly when only the case changes' do
|
|
SiteSetting.sso_overrides_username = true
|
|
|
|
sso = DiscourseSingleSignOn.new
|
|
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 'behaves properly when sso_overrides_username is set but username is missing or blank' do
|
|
SiteSetting.sso_overrides_username = true
|
|
|
|
sso = DiscourseSingleSignOn.new
|
|
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.sso_overrides_name = true
|
|
SiteSetting.sso_overrides_email = true
|
|
SiteSetting.sso_overrides_username = true
|
|
|
|
sso = DiscourseSingleSignOn.new
|
|
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 fill in data on way back" do
|
|
sso = make_sso
|
|
|
|
url, payload = sso.to_url.split("?")
|
|
expect(url).to eq sso.sso_url
|
|
parsed = SingleSignOn.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 = SingleSignOn.parse(payload, "supersecret")
|
|
|
|
test_parsed(parsed, sso)
|
|
end
|
|
|
|
it "validates nonce" do
|
|
_ , payload = DiscourseSingleSignOn.generate_url.split("?")
|
|
|
|
sso = DiscourseSingleSignOn.parse(payload)
|
|
expect(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 = DiscourseSingleSignOn.generate_url.split("?")
|
|
expect(url).to eq @sso_url
|
|
|
|
sso = DiscourseSingleSignOn.parse(payload)
|
|
expect(sso.nonce).to_not be_nil
|
|
end
|
|
|
|
context 'trusting emails' do
|
|
let(:sso) do
|
|
sso = DiscourseSingleSignOn.new
|
|
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)
|
|
end
|
|
|
|
it 'does not deactivate user if email provided is capitalized' do
|
|
SiteSetting.email_editable = false
|
|
SiteSetting.sso_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.sso_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
|
|
|
|
context 'welcome emails' do
|
|
let(:sso) {
|
|
sso = DiscourseSingleSignOn.new
|
|
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
|
|
|
|
context 'setting title for a user' do
|
|
let(:sso) {
|
|
sso = DiscourseSingleSignOn.new
|
|
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
|
|
|
|
context 'setting bio for a user' do
|
|
let(:sso) do
|
|
sso = DiscourseSingleSignOn.new
|
|
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.sso_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 sso_overrides_avatar is not enabled' do
|
|
|
|
it "correctly handles provided avatar_urls" do
|
|
sso = DiscourseSingleSignOn.new
|
|
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"))
|
|
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"))
|
|
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 sso_overrides_avatar is enabled' do
|
|
let!(:sso_record) { Fabricate(:single_sign_on_record, external_avatar_url: "http://example.com/an_image.png") }
|
|
|
|
let!(:sso) {
|
|
sso = DiscourseSingleSignOn.new
|
|
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.sso_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
|
|
end
|