mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
FEATURE: allow multiple secrets for Discourse SSO provider
This splits off the logic between SSO keys used incoming vs outgoing, it allows to far better restrict who is allowed to log in using a site. This allows for better auditing of the SSO provider feature
This commit is contained in:
@@ -521,153 +521,6 @@ RSpec.describe SessionController do
|
||||
expect(response.status).to eq(419)
|
||||
end
|
||||
|
||||
describe 'can act as an SSO provider' do
|
||||
before do
|
||||
stub_request(:any, /#{Discourse.current_hostname}\/uploads/).to_return(
|
||||
status: 200,
|
||||
body: lambda { |request| file_from_fixtures("logo.png") }
|
||||
)
|
||||
|
||||
SiteSetting.enable_sso_provider = true
|
||||
SiteSetting.enable_sso = false
|
||||
SiteSetting.enable_local_logins = true
|
||||
SiteSetting.sso_secret = "topsecret"
|
||||
|
||||
@sso = SingleSignOn.new
|
||||
@sso.nonce = "mynonce"
|
||||
@sso.sso_secret = SiteSetting.sso_secret
|
||||
@sso.return_sso_url = "http://somewhere.over.rainbow/sso"
|
||||
|
||||
@user = Fabricate(:user, password: "myfrogs123ADMIN", active: true, admin: true)
|
||||
group = Fabricate(:group)
|
||||
group.add(@user)
|
||||
|
||||
@user.create_user_avatar!
|
||||
UserAvatar.import_url_for_user(logo_fixture, @user)
|
||||
UserProfile.import_url_for_user(logo_fixture, @user, is_card_background: false)
|
||||
UserProfile.import_url_for_user(logo_fixture, @user, is_card_background: true)
|
||||
|
||||
@user.reload
|
||||
@user.user_avatar.reload
|
||||
@user.user_profile.reload
|
||||
EmailToken.update_all(confirmed: true)
|
||||
end
|
||||
|
||||
it "successfully logs in and redirects user to return_sso_url when the user is not logged in" do
|
||||
get "/session/sso_provider", params: Rack::Utils.parse_query(@sso.payload), headers: headers
|
||||
|
||||
expect(response).to redirect_to("/login")
|
||||
|
||||
post "/session.json",
|
||||
params: { login: @user.username, password: "myfrogs123ADMIN" }, xhr: true
|
||||
location = response.cookies["sso_destination_url"]
|
||||
# javascript code will handle redirection of user to return_sso_url
|
||||
expect(location).to match(/^http:\/\/somewhere.over.rainbow\/sso/)
|
||||
|
||||
payload = location.split("?")[1]
|
||||
sso2 = SingleSignOn.parse(payload, "topsecret")
|
||||
|
||||
expect(sso2.email).to eq(@user.email)
|
||||
expect(sso2.name).to eq(@user.name)
|
||||
expect(sso2.username).to eq(@user.username)
|
||||
expect(sso2.external_id).to eq(@user.id.to_s)
|
||||
expect(sso2.admin).to eq(true)
|
||||
expect(sso2.moderator).to eq(false)
|
||||
expect(sso2.groups).to eq(@user.groups.pluck(:name).join(","))
|
||||
|
||||
expect(sso2.avatar_url.blank?).to_not eq(true)
|
||||
expect(sso2.profile_background_url.blank?).to_not eq(true)
|
||||
expect(sso2.card_background_url.blank?).to_not eq(true)
|
||||
|
||||
expect(sso2.avatar_url).to start_with(Discourse.base_url)
|
||||
expect(sso2.profile_background_url).to start_with(Discourse.base_url)
|
||||
expect(sso2.card_background_url).to start_with(Discourse.base_url)
|
||||
end
|
||||
|
||||
it "successfully redirects user to return_sso_url when the user is logged in" do
|
||||
sign_in(@user)
|
||||
|
||||
get "/session/sso_provider", params: Rack::Utils.parse_query(@sso.payload), headers: headers
|
||||
|
||||
location = response.header["Location"]
|
||||
expect(location).to match(/^http:\/\/somewhere.over.rainbow\/sso/)
|
||||
|
||||
payload = location.split("?")[1]
|
||||
sso2 = SingleSignOn.parse(payload, "topsecret")
|
||||
|
||||
expect(sso2.email).to eq(@user.email)
|
||||
expect(sso2.name).to eq(@user.name)
|
||||
expect(sso2.username).to eq(@user.username)
|
||||
expect(sso2.external_id).to eq(@user.id.to_s)
|
||||
expect(sso2.admin).to eq(true)
|
||||
expect(sso2.moderator).to eq(false)
|
||||
expect(sso2.groups).to eq(@user.groups.pluck(:name).join(","))
|
||||
|
||||
expect(sso2.avatar_url.blank?).to_not eq(true)
|
||||
expect(sso2.profile_background_url.blank?).to_not eq(true)
|
||||
expect(sso2.card_background_url.blank?).to_not eq(true)
|
||||
|
||||
expect(sso2.avatar_url).to start_with(Discourse.base_url)
|
||||
expect(sso2.profile_background_url).to start_with(Discourse.base_url)
|
||||
expect(sso2.card_background_url).to start_with(Discourse.base_url)
|
||||
end
|
||||
|
||||
it 'handles non local content correctly' do
|
||||
SiteSetting.avatar_sizes = "100|49"
|
||||
SiteSetting.enable_s3_uploads = true
|
||||
SiteSetting.s3_access_key_id = "XXX"
|
||||
SiteSetting.s3_secret_access_key = "XXX"
|
||||
SiteSetting.s3_upload_bucket = "test"
|
||||
SiteSetting.s3_cdn_url = "http://cdn.com"
|
||||
|
||||
stub_request(:any, /test.s3.dualstack.us-east-1.amazonaws.com/).to_return(status: 200, body: "", headers: {})
|
||||
|
||||
@user.create_user_avatar!
|
||||
upload = Fabricate(:upload, url: "//test.s3.dualstack.us-east-1.amazonaws.com/something")
|
||||
|
||||
Fabricate(:optimized_image,
|
||||
sha1: SecureRandom.hex << "A" * 8,
|
||||
upload: upload,
|
||||
width: 98,
|
||||
height: 98,
|
||||
url: "//test.s3.amazonaws.com/something/else"
|
||||
)
|
||||
|
||||
@user.update_columns(uploaded_avatar_id: upload.id)
|
||||
@user.user_profile.update_columns(
|
||||
profile_background: "//test.s3.dualstack.us-east-1.amazonaws.com/something",
|
||||
card_background: "//test.s3.dualstack.us-east-1.amazonaws.com/something"
|
||||
)
|
||||
|
||||
@user.reload
|
||||
@user.user_avatar.reload
|
||||
@user.user_profile.reload
|
||||
|
||||
sign_in(@user)
|
||||
|
||||
stub_request(:get, "http://cdn.com/something/else").to_return(
|
||||
body: lambda { |request| File.new(Rails.root + 'spec/fixtures/images/logo.png') }
|
||||
)
|
||||
|
||||
get "/session/sso_provider", params: Rack::Utils.parse_query(@sso.payload), headers: headers
|
||||
|
||||
location = response.header["Location"]
|
||||
# javascript code will handle redirection of user to return_sso_url
|
||||
expect(location).to match(/^http:\/\/somewhere.over.rainbow\/sso/)
|
||||
|
||||
payload = location.split("?")[1]
|
||||
sso2 = SingleSignOn.parse(payload, "topsecret")
|
||||
|
||||
expect(sso2.avatar_url.blank?).to_not eq(true)
|
||||
expect(sso2.profile_background_url.blank?).to_not eq(true)
|
||||
expect(sso2.card_background_url.blank?).to_not eq(true)
|
||||
|
||||
expect(sso2.avatar_url).to start_with("#{SiteSetting.s3_cdn_url}/original")
|
||||
expect(sso2.profile_background_url).to start_with(SiteSetting.s3_cdn_url)
|
||||
expect(sso2.card_background_url).to start_with(SiteSetting.s3_cdn_url)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'local attribute override from SSO payload' do
|
||||
before do
|
||||
SiteSetting.email_editable = false
|
||||
@@ -724,91 +577,159 @@ RSpec.describe SessionController do
|
||||
end
|
||||
|
||||
describe '#sso_provider' do
|
||||
before do
|
||||
stub_request(:any, /#{Discourse.current_hostname}\/uploads/).to_return(
|
||||
status: 200,
|
||||
body: lambda { |request| file_from_fixtures("logo.png") }
|
||||
)
|
||||
let(:headers) { { host: Discourse.current_hostname } }
|
||||
|
||||
SiteSetting.enable_sso_provider = true
|
||||
SiteSetting.enable_sso = false
|
||||
SiteSetting.enable_local_logins = true
|
||||
SiteSetting.sso_secret = "topsecret"
|
||||
describe 'can act as an SSO provider' do
|
||||
before do
|
||||
stub_request(:any, /#{Discourse.current_hostname}\/uploads/).to_return(
|
||||
status: 200,
|
||||
body: lambda { |request| file_from_fixtures("logo.png") }
|
||||
)
|
||||
|
||||
@sso = SingleSignOn.new
|
||||
@sso.nonce = "mynonce"
|
||||
@sso.sso_secret = SiteSetting.sso_secret
|
||||
@sso.return_sso_url = "http://somewhere.over.rainbow/sso"
|
||||
SiteSetting.enable_sso_provider = true
|
||||
SiteSetting.enable_sso = false
|
||||
SiteSetting.enable_local_logins = true
|
||||
SiteSetting.sso_provider_secrets = "www.random.site|secretForRandomSite\nsomewhere.over.rainbow|secretForOverRainbow"
|
||||
|
||||
@user = Fabricate(:user, password: "myfrogs123ADMIN", active: true, admin: true)
|
||||
@user.create_user_avatar!
|
||||
UserAvatar.import_url_for_user(logo_fixture, @user)
|
||||
UserProfile.import_url_for_user(logo_fixture, @user, is_card_background: false)
|
||||
UserProfile.import_url_for_user(logo_fixture, @user, is_card_background: true)
|
||||
@sso = SingleSignOn.new
|
||||
@sso.nonce = "mynonce"
|
||||
@sso.return_sso_url = "http://somewhere.over.rainbow/sso"
|
||||
|
||||
@user.reload
|
||||
@user.user_avatar.reload
|
||||
@user.user_profile.reload
|
||||
EmailToken.update_all(confirmed: true)
|
||||
end
|
||||
@user = Fabricate(:user, password: "myfrogs123ADMIN", active: true, admin: true)
|
||||
group = Fabricate(:group)
|
||||
group.add(@user)
|
||||
|
||||
it "successfully logs in and redirects user to return_sso_url when the user is not logged in" do
|
||||
get "/session/sso_provider", params: Rack::Utils.parse_query(@sso.payload)
|
||||
expect(response).to redirect_to("/login")
|
||||
@user.create_user_avatar!
|
||||
UserAvatar.import_url_for_user(logo_fixture, @user)
|
||||
UserProfile.import_url_for_user(logo_fixture, @user, is_card_background: false)
|
||||
UserProfile.import_url_for_user(logo_fixture, @user, is_card_background: true)
|
||||
|
||||
post "/session.json",
|
||||
params: { login: @user.username, password: "myfrogs123ADMIN" },
|
||||
xhr: true
|
||||
@user.reload
|
||||
@user.user_avatar.reload
|
||||
@user.user_profile.reload
|
||||
EmailToken.update_all(confirmed: true)
|
||||
end
|
||||
|
||||
location = response.cookies["sso_destination_url"]
|
||||
# javascript code will handle redirection of user to return_sso_url
|
||||
expect(location).to match(/^http:\/\/somewhere.over.rainbow\/sso/)
|
||||
it "successfully logs in and redirects user to return_sso_url when the user is not logged in" do
|
||||
get "/session/sso_provider", params: Rack::Utils.parse_query(@sso.payload("secretForOverRainbow"))
|
||||
|
||||
payload = location.split("?")[1]
|
||||
sso2 = SingleSignOn.parse(payload, "topsecret")
|
||||
expect(response).to redirect_to("/login")
|
||||
|
||||
expect(sso2.email).to eq(@user.email)
|
||||
expect(sso2.name).to eq(@user.name)
|
||||
expect(sso2.username).to eq(@user.username)
|
||||
expect(sso2.external_id).to eq(@user.id.to_s)
|
||||
expect(sso2.admin).to eq(true)
|
||||
expect(sso2.moderator).to eq(false)
|
||||
expect(sso2.groups).to eq(@user.groups.pluck(:name).join(","))
|
||||
post "/session.json",
|
||||
params: { login: @user.username, password: "myfrogs123ADMIN" }, xhr: true, headers: headers
|
||||
|
||||
expect(sso2.avatar_url.blank?).to_not eq(true)
|
||||
expect(sso2.profile_background_url.blank?).to_not eq(true)
|
||||
expect(sso2.card_background_url.blank?).to_not eq(true)
|
||||
location = response.cookies["sso_destination_url"]
|
||||
# javascript code will handle redirection of user to return_sso_url
|
||||
expect(location).to match(/^http:\/\/somewhere.over.rainbow\/sso/)
|
||||
|
||||
expect(sso2.avatar_url).to start_with(Discourse.base_url)
|
||||
expect(sso2.profile_background_url).to start_with(Discourse.base_url)
|
||||
expect(sso2.card_background_url).to start_with(Discourse.base_url)
|
||||
end
|
||||
payload = location.split("?")[1]
|
||||
sso2 = SingleSignOn.parse(payload)
|
||||
|
||||
it "successfully redirects user to return_sso_url when the user is logged in" do
|
||||
sign_in(@user)
|
||||
expect(sso2.email).to eq(@user.email)
|
||||
expect(sso2.name).to eq(@user.name)
|
||||
expect(sso2.username).to eq(@user.username)
|
||||
expect(sso2.external_id).to eq(@user.id.to_s)
|
||||
expect(sso2.admin).to eq(true)
|
||||
expect(sso2.moderator).to eq(false)
|
||||
expect(sso2.groups).to eq(@user.groups.pluck(:name).join(","))
|
||||
|
||||
get "/session/sso_provider", params: Rack::Utils.parse_query(@sso.payload)
|
||||
expect(sso2.avatar_url.blank?).to_not eq(true)
|
||||
expect(sso2.profile_background_url.blank?).to_not eq(true)
|
||||
expect(sso2.card_background_url.blank?).to_not eq(true)
|
||||
|
||||
location = response.header["Location"]
|
||||
expect(location).to match(/^http:\/\/somewhere.over.rainbow\/sso/)
|
||||
expect(sso2.avatar_url).to start_with(Discourse.base_url)
|
||||
expect(sso2.profile_background_url).to start_with(Discourse.base_url)
|
||||
expect(sso2.card_background_url).to start_with(Discourse.base_url)
|
||||
end
|
||||
|
||||
payload = location.split("?")[1]
|
||||
sso2 = SingleSignOn.parse(payload, "topsecret")
|
||||
it "it fails to log in if secret is wrong" do
|
||||
get "/session/sso_provider", params: Rack::Utils.parse_query(@sso.payload("secretForRandomSite"))
|
||||
|
||||
expect(sso2.email).to eq(@user.email)
|
||||
expect(sso2.name).to eq(@user.name)
|
||||
expect(sso2.username).to eq(@user.username)
|
||||
expect(sso2.external_id).to eq(@user.id.to_s)
|
||||
expect(sso2.admin).to eq(true)
|
||||
expect(sso2.moderator).to eq(false)
|
||||
expect(response.status).to eq(500)
|
||||
end
|
||||
|
||||
expect(sso2.avatar_url.blank?).to_not eq(true)
|
||||
expect(sso2.profile_background_url.blank?).to_not eq(true)
|
||||
expect(sso2.card_background_url.blank?).to_not eq(true)
|
||||
it "successfully redirects user to return_sso_url when the user is logged in" do
|
||||
sign_in(@user)
|
||||
|
||||
expect(sso2.avatar_url).to start_with("#{Discourse.store.absolute_base_url}/original")
|
||||
expect(sso2.profile_background_url).to start_with(Discourse.base_url)
|
||||
expect(sso2.card_background_url).to start_with(Discourse.base_url)
|
||||
get "/session/sso_provider", params: Rack::Utils.parse_query(@sso.payload("secretForOverRainbow"))
|
||||
|
||||
location = response.header["Location"]
|
||||
expect(location).to match(/^http:\/\/somewhere.over.rainbow\/sso/)
|
||||
|
||||
payload = location.split("?")[1]
|
||||
sso2 = SingleSignOn.parse(payload)
|
||||
|
||||
expect(sso2.email).to eq(@user.email)
|
||||
expect(sso2.name).to eq(@user.name)
|
||||
expect(sso2.username).to eq(@user.username)
|
||||
expect(sso2.external_id).to eq(@user.id.to_s)
|
||||
expect(sso2.admin).to eq(true)
|
||||
expect(sso2.moderator).to eq(false)
|
||||
expect(sso2.groups).to eq(@user.groups.pluck(:name).join(","))
|
||||
|
||||
expect(sso2.avatar_url.blank?).to_not eq(true)
|
||||
expect(sso2.profile_background_url.blank?).to_not eq(true)
|
||||
expect(sso2.card_background_url.blank?).to_not eq(true)
|
||||
|
||||
expect(sso2.avatar_url).to start_with(Discourse.base_url)
|
||||
expect(sso2.profile_background_url).to start_with(Discourse.base_url)
|
||||
expect(sso2.card_background_url).to start_with(Discourse.base_url)
|
||||
end
|
||||
|
||||
it 'handles non local content correctly' do
|
||||
SiteSetting.avatar_sizes = "100|49"
|
||||
SiteSetting.enable_s3_uploads = true
|
||||
SiteSetting.s3_access_key_id = "XXX"
|
||||
SiteSetting.s3_secret_access_key = "XXX"
|
||||
SiteSetting.s3_upload_bucket = "test"
|
||||
SiteSetting.s3_cdn_url = "http://cdn.com"
|
||||
|
||||
stub_request(:any, /test.s3.dualstack.us-east-1.amazonaws.com/).to_return(status: 200, body: "", headers: { referer: "fgdfds" })
|
||||
|
||||
@user.create_user_avatar!
|
||||
upload = Fabricate(:upload, url: "//test.s3.dualstack.us-east-1.amazonaws.com/something")
|
||||
|
||||
Fabricate(:optimized_image,
|
||||
sha1: SecureRandom.hex << "A" * 8,
|
||||
upload: upload,
|
||||
width: 98,
|
||||
height: 98,
|
||||
url: "//test.s3.amazonaws.com/something/else"
|
||||
)
|
||||
|
||||
@user.update_columns(uploaded_avatar_id: upload.id)
|
||||
@user.user_profile.update_columns(
|
||||
profile_background: "//test.s3.dualstack.us-east-1.amazonaws.com/something",
|
||||
card_background: "//test.s3.dualstack.us-east-1.amazonaws.com/something"
|
||||
)
|
||||
|
||||
@user.reload
|
||||
@user.user_avatar.reload
|
||||
@user.user_profile.reload
|
||||
|
||||
sign_in(@user)
|
||||
|
||||
stub_request(:get, "http://cdn.com/something/else").to_return(
|
||||
body: lambda { |request| File.new(Rails.root + 'spec/fixtures/images/logo.png') }
|
||||
)
|
||||
|
||||
get "/session/sso_provider", params: Rack::Utils.parse_query(@sso.payload("secretForOverRainbow"))
|
||||
|
||||
location = response.header["Location"]
|
||||
# javascript code will handle redirection of user to return_sso_url
|
||||
expect(location).to match(/^http:\/\/somewhere.over.rainbow\/sso/)
|
||||
|
||||
payload = location.split("?")[1]
|
||||
sso2 = SingleSignOn.parse(payload)
|
||||
|
||||
expect(sso2.avatar_url.blank?).to_not eq(true)
|
||||
expect(sso2.profile_background_url.blank?).to_not eq(true)
|
||||
expect(sso2.card_background_url.blank?).to_not eq(true)
|
||||
|
||||
expect(sso2.avatar_url).to start_with("#{SiteSetting.s3_cdn_url}/original")
|
||||
expect(sso2.profile_background_url).to start_with(SiteSetting.s3_cdn_url)
|
||||
expect(sso2.card_background_url).to start_with(SiteSetting.s3_cdn_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user