Add multi-tenancy support for SAML

This commit is contained in:
Anamika Aggarwal
2025-07-16 07:49:37 +00:00
committed by Javi Martín
parent 5726bcef07
commit c9bf7797a0
5 changed files with 156 additions and 4 deletions

View File

@@ -16,6 +16,11 @@ module OmniauthTenantSetup
oauth2(env, secrets.wordpress_oauth2_key, secrets.wordpress_oauth2_secret) oauth2(env, secrets.wordpress_oauth2_key, secrets.wordpress_oauth2_secret)
end end
def saml(env)
saml_auth(env, secrets.saml_sp_entity_id,
secrets.saml_idp_metadata_url, secrets.saml_idp_sso_service_url)
end
private private
def oauth(env, key, secret) def oauth(env, key, secret)
@@ -32,6 +37,24 @@ module OmniauthTenantSetup
end end
end end
def saml_auth(env, sp_entity_id, idp_metadata_url, idp_sso_service_url)
unless Tenant.default?
strategy = env["omniauth.strategy"]
strategy.options[:sp_entity_id] = sp_entity_id if sp_entity_id.present?
strategy.options[:idp_metadata_url] = idp_metadata_url if idp_metadata_url.present?
strategy.options[:idp_sso_service_url] = idp_sso_service_url if idp_sso_service_url.present?
if strategy.options[:issuer].present? && sp_entity_id.present?
strategy.options[:issuer] = sp_entity_id
end
if strategy.options[:idp_metadata].present? && idp_metadata_url.present?
strategy.options[:idp_metadata] = idp_metadata_url
end
end
end
def secrets def secrets
Tenant.current_secrets Tenant.current_secrets
end end

View File

@@ -294,7 +294,7 @@ Devise.setup do |config|
saml_settings[:sp_entity_id] = Rails.application.secrets.saml_sp_entity_id saml_settings[:sp_entity_id] = Rails.application.secrets.saml_sp_entity_id
saml_settings[:allowed_clock_drift] = 1.minute saml_settings[:allowed_clock_drift] = 1.minute
end end
config.omniauth :saml, saml_settings config.omniauth :saml, saml_settings.merge(setup: ->(env) { OmniauthTenantSetup.saml(env) })
# ==> Warden configuration # ==> Warden configuration
# If you want to use other strategies, that are not supported by Devise, or # If you want to use other strategies, that are not supported by Devise, or

View File

@@ -81,7 +81,7 @@ staging:
# secret_key: my_secret_value # secret_key: my_secret_value
# #
# Currently you can overwrite SMTP, SMS, manager, microsoft API, # Currently you can overwrite SMTP, SMS, manager, microsoft API,
# HTTP basic, twitter, facebook, google, wordpress and security settings. # HTTP basic, twitter, facebook, google, wordpress, SAML and security settings.
twitter_key: "" twitter_key: ""
twitter_secret: "" twitter_secret: ""
facebook_key: "" facebook_key: ""
@@ -140,7 +140,7 @@ preproduction:
# secret_key: my_secret_value # secret_key: my_secret_value
# #
# Currently you can overwrite SMTP, SMS, manager, microsoft API, # Currently you can overwrite SMTP, SMS, manager, microsoft API,
# HTTP basic, twitter, facebook, google, wordpress and security settings. # HTTP basic, twitter, facebook, google, wordpress, SAML and security settings.
twitter_key: "" twitter_key: ""
twitter_secret: "" twitter_secret: ""
facebook_key: "" facebook_key: ""
@@ -198,7 +198,7 @@ production:
# secret_key: my_secret_value # secret_key: my_secret_value
# #
# Currently you can overwrite SMTP, SMS, manager, microsoft API, # Currently you can overwrite SMTP, SMS, manager, microsoft API,
# HTTP basic, twitter, facebook, google, wordpress and security settings. # HTTP basic, twitter, facebook, google, wordpress, SAML and security settings.
twitter_key: "" twitter_key: ""
twitter_secret: "" twitter_secret: ""
facebook_key: "" facebook_key: ""

View File

@@ -0,0 +1,87 @@
require "rails_helper"
describe OmniauthTenantSetup do
describe "#saml" do
it "uses different secrets for different tenants" do
create(:tenant, schema: "mars")
create(:tenant, schema: "venus")
stub_secrets(
saml_sp_entity_id: "https://default.consul.dev/saml/metadata",
saml_idp_metadata_url: "https://default-idp.example.com/metadata",
saml_idp_sso_service_url: "https://default-idp.example.com/sso",
tenants: {
mars: {
saml_sp_entity_id: "https://mars.consul.dev/saml/metadata",
saml_idp_metadata_url: "https://mars-idp.example.com/metadata",
saml_idp_sso_service_url: "https://mars-idp.example.com/sso"
},
venus: {
saml_sp_entity_id: "https://venus.consul.dev/saml/metadata",
saml_idp_metadata_url: "https://venus-idp.example.com/metadata",
saml_idp_sso_service_url: "https://venus-idp.example.com/sso"
}
}
)
Tenant.switch("mars") do
mars_env = {
"omniauth.strategy" => double(options: {}),
"HTTP_HOST" => "mars.consul.dev"
}
OmniauthTenantSetup.saml(mars_env)
mars_strategy_options = mars_env["omniauth.strategy"].options
expect(mars_strategy_options[:sp_entity_id]).to eq "https://mars.consul.dev/saml/metadata"
expect(mars_strategy_options[:idp_metadata_url]).to eq "https://mars-idp.example.com/metadata"
expect(mars_strategy_options[:idp_sso_service_url]).to eq "https://mars-idp.example.com/sso"
end
Tenant.switch("venus") do
venus_env = {
"omniauth.strategy" => double(options: {}),
"HTTP_HOST" => "venus.consul.dev"
}
OmniauthTenantSetup.saml(venus_env)
venus_strategy_options = venus_env["omniauth.strategy"].options
expect(venus_strategy_options[:sp_entity_id]).to eq "https://venus.consul.dev/saml/metadata"
expect(venus_strategy_options[:idp_metadata_url]).to eq "https://venus-idp.example.com/metadata"
expect(venus_strategy_options[:idp_sso_service_url]).to eq "https://venus-idp.example.com/sso"
end
end
it "uses default secrets for non-overridden tenant" do
create(:tenant, schema: "earth")
stub_secrets(
saml_sp_entity_id: "https://default.consul.dev/saml/metadata",
saml_idp_metadata_url: "https://default-idp.example.com/metadata",
saml_idp_sso_service_url: "https://default-idp.example.com/sso",
tenants: {
mars: {
saml_sp_entity_id: "https://mars.consul.dev/saml/metadata",
saml_idp_metadata_url: "https://mars-idp.example.com/metadata",
saml_idp_sso_service_url: "https://mars-idp.example.com/sso"
}
}
)
Tenant.switch("earth") do
earth_env = {
"omniauth.strategy" => double(options: {}),
"HTTP_HOST" => "earth.consul.dev"
}
OmniauthTenantSetup.saml(earth_env)
earth_strategy_options = earth_env["omniauth.strategy"].options
expect(earth_strategy_options[:sp_entity_id]).to eq "https://default.consul.dev/saml/metadata"
expect(earth_strategy_options[:idp_metadata_url]).to eq "https://default-idp.example.com/metadata"
expect(earth_strategy_options[:idp_sso_service_url]).to eq "https://default-idp.example.com/sso"
end
end
end
end

View File

@@ -602,6 +602,48 @@ describe "Users" do
expect(page).to have_field "Email", with: "tester@consul.dev" expect(page).to have_field "Email", with: "tester@consul.dev"
end end
scenario "SAML user from one tenant cannot sign in to another tenant", :seed_tenants do
%w[mars venus].each do |schema|
create(:tenant, schema: schema)
Tenant.switch(schema) { Setting["feature.saml_login"] = true }
end
Tenant.switch("mars") do
mars_user = create(:user, username: "marsuser", email: "mars@consul.dev")
create(:identity, uid: "mars-saml-123", provider: "saml", user: mars_user)
end
mars_saml_hash = {
provider: "saml",
uid: "mars-saml-123",
info: {
name: "marsuser",
email: "mars@consul.dev"
}
}
OmniAuth.config.add_mock(:saml, mars_saml_hash)
with_subdomain("mars") do
visit new_user_session_path
click_button "Sign in with SAML"
expect(page).to have_content "Successfully identified as Saml"
within("#notice") { click_button "Close" }
click_link "My account"
expect(page).to have_field "Username", with: "marsuser"
end
with_subdomain("venus") do
visit new_user_session_path
click_button "Sign in with SAML"
expect(page).to have_content "To continue, please click on the confirmation " \
"link that we have sent you via email"
end
end
end end
end end