Merge pull request #6010 from Anamika1608/saml

Add support for SAML authentication
This commit is contained in:
Javi Martín
2025-07-23 15:02:28 +02:00
committed by GitHub
21 changed files with 377 additions and 31 deletions

View File

@@ -38,6 +38,7 @@ gem "omniauth", "~> 2.1.3"
gem "omniauth-facebook", "~> 10.0.0" gem "omniauth-facebook", "~> 10.0.0"
gem "omniauth-google-oauth2", "~> 1.2.1" gem "omniauth-google-oauth2", "~> 1.2.1"
gem "omniauth-rails_csrf_protection", "~> 1.0.2" gem "omniauth-rails_csrf_protection", "~> 1.0.2"
gem "omniauth-saml", "~> 2.2.4"
gem "omniauth-twitter", "~> 1.4.0" gem "omniauth-twitter", "~> 1.4.0"
gem "paranoia", "~> 3.0.1" gem "paranoia", "~> 3.0.1"
gem "pg", "~> 1.5.9" gem "pg", "~> 1.5.9"

View File

@@ -440,6 +440,9 @@ GEM
omniauth-rails_csrf_protection (1.0.2) omniauth-rails_csrf_protection (1.0.2)
actionpack (>= 4.2) actionpack (>= 4.2)
omniauth (~> 2.0) omniauth (~> 2.0)
omniauth-saml (2.2.4)
omniauth (~> 2.1)
ruby-saml (~> 1.18)
omniauth-twitter (1.4.0) omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1) omniauth-oauth (~> 1.1)
rack rack
@@ -609,6 +612,9 @@ GEM
rubocop-rspec (~> 3, >= 3.0.1) rubocop-rspec (~> 3, >= 3.0.1)
ruby-progressbar (1.13.0) ruby-progressbar (1.13.0)
ruby-rc4 (0.1.5) ruby-rc4 (0.1.5)
ruby-saml (1.18.0)
nokogiri (>= 1.13.10)
rexml
ruby-vips (2.2.3) ruby-vips (2.2.3)
ffi (~> 1.12) ffi (~> 1.12)
logger logger
@@ -797,6 +803,7 @@ DEPENDENCIES
omniauth-facebook (~> 10.0.0) omniauth-facebook (~> 10.0.0)
omniauth-google-oauth2 (~> 1.2.1) omniauth-google-oauth2 (~> 1.2.1)
omniauth-rails_csrf_protection (~> 1.0.2) omniauth-rails_csrf_protection (~> 1.0.2)
omniauth-saml (~> 2.2.4)
omniauth-twitter (~> 1.4.0) omniauth-twitter (~> 1.4.0)
paranoia (~> 3.0.1) paranoia (~> 3.0.1)
pdf-reader (~> 2.14.1) pdf-reader (~> 2.14.1)

View File

@@ -1392,7 +1392,8 @@ table {
.button.button-twitter, .button.button-twitter,
.button.button-facebook, .button.button-facebook,
.button.button-google, .button.button-google,
.button.button-wordpress { .button.button-wordpress,
.button.button-saml {
color: inherit; color: inherit;
font-weight: bold; font-weight: bold;
@@ -1442,6 +1443,17 @@ table {
} }
} }
.button.button-saml {
@include has-fa-icon(lock, solid);
background: #eafee9;
border-left: 3px solid #3b9857;
&::before {
color: #3b9857;
}
}
// 14. Verification // 14. Verification
// ---------------- // ----------------

View File

@@ -4,6 +4,7 @@ class Admin::Settings::FeaturesTabComponent < ApplicationComponent
feature.featured_proposals feature.featured_proposals
feature.facebook_login feature.facebook_login
feature.google_login feature.google_login
feature.saml_login
feature.twitter_login feature.twitter_login
feature.wordpress_login feature.wordpress_login
feature.signature_sheets feature.signature_sheets

View File

@@ -16,7 +16,8 @@ class Devise::OmniauthFormComponent < ApplicationComponent
(:twitter if feature?(:twitter_login)), (:twitter if feature?(:twitter_login)),
(:facebook if feature?(:facebook_login)), (:facebook if feature?(:facebook_login)),
(:google_oauth2 if feature?(:google_login)), (:google_oauth2 if feature?(:google_login)),
(:wordpress_oauth2 if feature?(:wordpress_login)) (:wordpress_oauth2 if feature?(:wordpress_login)),
(:saml if feature?(:saml_login))
].compact ].compact
end end
end end

View File

@@ -1,4 +1,6 @@
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
skip_before_action :verify_authenticity_token, only: :saml
def twitter def twitter
sign_in_with :twitter_login, :twitter sign_in_with :twitter_login, :twitter
end end
@@ -15,6 +17,10 @@ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
sign_in_with :wordpress_login, :wordpress_oauth2 sign_in_with :wordpress_login, :wordpress_oauth2
end end
def saml
sign_in_with :saml_login, :saml
end
def after_sign_in_path_for(resource) def after_sign_in_path_for(resource)
if resource.registering_with_oauth if resource.registering_with_oauth
finish_signup_path finish_signup_path

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

@@ -85,6 +85,7 @@ class Setting < ApplicationRecord
"feature.remote_census": nil, "feature.remote_census": nil,
"feature.valuation_comment_notification": true, "feature.valuation_comment_notification": true,
"feature.graphql_api": true, "feature.graphql_api": true,
"feature.saml_login": false,
"feature.sdg": true, "feature.sdg": true,
"feature.machine_learning": false, "feature.machine_learning": false,
"feature.remove_investments_supports": true, "feature.remove_investments_supports": true,

View File

@@ -286,6 +286,15 @@ Devise.setup do |config|
Rails.application.secrets.wordpress_oauth2_secret, Rails.application.secrets.wordpress_oauth2_secret,
client_options: { site: Rails.application.secrets.wordpress_oauth2_site }, client_options: { site: Rails.application.secrets.wordpress_oauth2_site },
setup: ->(env) { OmniauthTenantSetup.wordpress_oauth2(env) } setup: ->(env) { OmniauthTenantSetup.wordpress_oauth2(env) }
saml_settings = {}
if Rails.application.secrets.saml_idp_metadata_url.present?
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
saml_settings = idp_metadata_parser.parse_remote_to_hash(Rails.application.secrets.saml_idp_metadata_url)
saml_settings[:idp_sso_service_url] = Rails.application.secrets.saml_idp_sso_service_url
saml_settings[:sp_entity_id] = Rails.application.secrets.saml_sp_entity_id
saml_settings[:allowed_clock_drift] = 1.minute
end
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

@@ -278,6 +278,10 @@ en:
info: info:
sign_in: "Sign in with:" sign_in: "Sign in with:"
sign_up: "Sign up with:" sign_up: "Sign up with:"
saml:
sign_in: Sign in with SAML
sign_up: Sign up with SAML
name: SAML
or_fill: "Or fill the following form:" or_fill: "Or fill the following form:"
proposals: proposals:
create: create:

View File

@@ -91,6 +91,8 @@ en:
google_login_description: "Allow users to sign up with their Google Account" google_login_description: "Allow users to sign up with their Google Account"
wordpress_login: "Wordpress login" wordpress_login: "Wordpress login"
wordpress_login_description: "Allow users to sign up with their Wordpress Account" wordpress_login_description: "Allow users to sign up with their Wordpress Account"
saml_login: "SAML login"
saml_login_description: "Allow users to sign up with SAML"
featured_proposals: "Featured proposals" featured_proposals: "Featured proposals"
featured_proposals_description: "Shows featured proposals on index proposals page" featured_proposals_description: "Shows featured proposals on index proposals page"
signature_sheets: "Signature sheets" signature_sheets: "Signature sheets"

View File

@@ -275,6 +275,10 @@ es:
sign_in: Entra con Twitter sign_in: Entra con Twitter
sign_up: Regístrate con Twitter sign_up: Regístrate con Twitter
name: Twitter name: Twitter
saml:
sign_in: Entra con SAML
sign_up: Regístrate con SAML
name: SAML
info: info:
sign_in: "Entra con:" sign_in: "Entra con:"
sign_up: "Regístrate con:" sign_up: "Regístrate con:"

View File

@@ -91,6 +91,8 @@ es:
google_login_description: "Permitir que los usuarios se registren con su cuenta de Google" google_login_description: "Permitir que los usuarios se registren con su cuenta de Google"
wordpress_login: "Registro con Wordpress" wordpress_login: "Registro con Wordpress"
wordpress_login_description: "Permitir que los usuarios se registren con su cuenta de Wordpress" wordpress_login_description: "Permitir que los usuarios se registren con su cuenta de Wordpress"
saml_login: "Registro con SAML"
saml_login_description: "Permitir que los usuarios se registren usando SAML"
featured_proposals: "Propuestas destacadas" featured_proposals: "Propuestas destacadas"
featured_proposals_description: "Muestra propuestas destacadas en la página principal de propuestas" featured_proposals_description: "Muestra propuestas destacadas en la página principal de propuestas"
signature_sheets: "Hojas de firmas" signature_sheets: "Hojas de firmas"

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: ""
@@ -91,6 +91,9 @@ staging:
wordpress_oauth2_key: "" wordpress_oauth2_key: ""
wordpress_oauth2_secret: "" wordpress_oauth2_secret: ""
wordpress_oauth2_site: "" wordpress_oauth2_site: ""
saml_sp_entity_id: ""
saml_idp_metadata_url: ""
saml_idp_sso_service_url: ""
<<: *maps <<: *maps
<<: *apis <<: *apis
@@ -137,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: ""
@@ -147,6 +150,9 @@ preproduction:
wordpress_oauth2_key: "" wordpress_oauth2_key: ""
wordpress_oauth2_secret: "" wordpress_oauth2_secret: ""
wordpress_oauth2_site: "" wordpress_oauth2_site: ""
saml_sp_entity_id: ""
saml_idp_metadata_url: ""
saml_idp_sso_service_url: ""
<<: *maps <<: *maps
<<: *apis <<: *apis
@@ -192,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: ""
@@ -202,5 +208,8 @@ production:
wordpress_oauth2_key: "" wordpress_oauth2_key: ""
wordpress_oauth2_secret: "" wordpress_oauth2_secret: ""
wordpress_oauth2_site: "" wordpress_oauth2_site: ""
saml_sp_entity_id: ""
saml_idp_metadata_url: ""
saml_idp_sso_service_url: ""
<<: *maps <<: *maps
<<: *apis <<: *apis

View File

@@ -44,7 +44,7 @@
* [Tests](customization/tests.md) * [Tests](customization/tests.md)
* [Technical Features](features/features.md) * [Technical Features](features/features.md)
* [OAuth](features/oauth.md) * [Authentication with external services (OAuth)](features/oauth.md)
* [GraphQL](features/graphql.md) * [GraphQL](features/graphql.md)
* [Debates and proposals recommendations](features/recommendations.md) * [Debates and proposals recommendations](features/recommendations.md)
* [Configure Census Connection](features/census_configuration.md) * [Configure Census Connection](features/census_configuration.md)

View File

@@ -1,24 +1,26 @@
# OAuth # Authentication with external services (OAuth)
You can configure authentication services with external OAuth providers, right now Twitter, Facebook, Google and Wordpress are supported. You can configure authentication services with external OAuth providers. Right now, Twitter, Facebook, Google, Wordpress and SAML are supported.
## 1. Create an App on the platform ## 1. Create an App on the platform
For each platform, go to their developers section and follow their guides to create an app. For Twitter, Facebook, Google and Wordpress, go to their developers section and follow their guides to create an app. For SAML, you'll have to configure an Identity Provider (IdP).
## 2. Set the authentication URL of your Consul Democracy installation ## 2. Set the authentication URL of your Consul Democracy installation
They'll ask you for the authentication URL of your Consul Democracy installation, and as you can see running `rails routes |grep omniauth` at your Consul Democracy repo locally: They'll ask you for the authentication URL of your Consul Democracy installation, and as you can see running `rails routes | grep omniauth` at your Consul Democracy repo locally:
```bash ```bash
user_twitter_omniauth_authorize GET|POST /users/auth/twitter(.:format) users/omniauth_callbacks#passthru user_twitter_omniauth_authorize GET|POST /users/auth/twitter(.:format) users/omniauth_callbacks#passthru
user_twitter_omniauth_callback GET|POST /users/auth/twitter/callback(.:format) users/omniauth_callbacks#twitter user_twitter_omniauth_callback GET|POST /users/auth/twitter/callback(.:format) users/omniauth_callbacks#twitter
user_facebook_omniauth_authorize GET|POST /users/auth/facebook(.:format) users/omniauth_callbacks#passthru user_facebook_omniauth_authorize GET|POST /users/auth/facebook(.:format) users/omniauth_callbacks#passthru
user_facebook_omniauth_callback GET|POST /users/auth/facebook/callback(.:format) users/omniauth_callbacks#facebook user_facebook_omniauth_callback GET|POST /users/auth/facebook/callback(.:format) users/omniauth_callbacks#facebook
user_google_oauth2_omniauth_authorize GET|POST /users/auth/google_oauth2(.:format) users omniauth_callbacks#passthru user_google_oauth2_omniauth_authorize GET|POST /users/auth/google_oauth2(.:format) users/omniauth_callbacks#passthru
user_google_oauth2_omniauth_callback GET|POST /users/auth/google_oauth2/callback(.:format) users/omniauth_callbacks#google_oauth2 user_google_oauth2_omniauth_callback GET|POST /users/auth/google_oauth2/callback(.:format) users/omniauth_callbacks#google_oauth2
user_wordpress_oauth2_omniauth_authorize GET|POST /users/auth/wordpress_oauth2(.:format) users/omniauth_callbacks#passthru user_wordpress_oauth2_omniauth_authorize GET|POST /users/auth/wordpress_oauth2(.:format) users/omniauth_callbacks#passthru
user_wordpress_oauth2_omniauth_callback GET|POST /users/auth/wordpress_oauth2/callback(.:format) users/omniauth_callbacks#wordpress_oauth2 user_wordpress_oauth2_omniauth_callback GET|POST /users/auth/wordpress_oauth2/callback(.:format) users/omniauth_callbacks#wordpress_oauth2
user_saml_omniauth_authorize GET|POST /users/auth/saml(.:format) users/omniauth_callbacks#passthru
user_saml_omniauth_callback GET|POST /users/auth/saml/callback(.:format) users/omniauth_callbacks#saml
``` ```
So for example the URL for Facebook application would be `yourdomain.com/users/auth/facebook/callback`. So for example the URL for Facebook application would be `yourdomain.com/users/auth/facebook/callback`.
@@ -37,4 +39,7 @@ When you complete the application registration you'll get a *key* and *secret* v
wordpress_oauth2_key: "" wordpress_oauth2_key: ""
wordpress_oauth2_secret: "" wordpress_oauth2_secret: ""
wordpress_oauth2_site: "" wordpress_oauth2_site: ""
saml_sp_entity_id: "https://yoursp.org/entityid"
saml_idp_metadata_url: "https://youridp.org/api/saml/metadata"
saml_idp_sso_service_url: "https://youridp.org/api/saml/sso"
``` ```

View File

@@ -44,7 +44,7 @@
* [Tests](customization/tests.md) * [Tests](customization/tests.md)
* [Funcionalidades Técnicas](features/features.md) * [Funcionalidades Técnicas](features/features.md)
* [OAuth](features/oauth.md) * [Autenticación con servicios externos (OAuth)](features/oauth.md)
* [GraphQL](features/graphql.md) * [GraphQL](features/graphql.md)
* [Recomendaciones de debates y propuestas](features/recommendations.md) * [Recomendaciones de debates y propuestas](features/recommendations.md)
* [Configurar conexión con el Censo](features/census_configuration.md) * [Configurar conexión con el Censo](features/census_configuration.md)

View File

@@ -1,24 +1,26 @@
# OAuth # Autenticación con servicios externos (OAuth)
Puedes configurar la autenticación con servicios externos usando OAuth, actualmente se pueden utilizar Twitter, Facebook, Google y Wordpress. Puedes configurar la autenticación con servicios externos usando OAuth. Actualmente, se pueden utilizar Twitter, Facebook, Google, Wordpress y SAML.
## 1. Crea una aplicación en la plataforma ## 1. Crea una aplicación en la plataforma
Para cada plataforma, sigue las instrucciones en la sección de desarrolladores de su página web. Para Twitter, Facebook, Google y Wordpress, sigue las instrucciones en la sección de desarrolladores de su página web. Para SAML, tendrás que configurar tu propio proveedor de identidad (IdP).
## 2. Establece la URL de autenticación de tu instalación de Consul Democracy ## 2. Establece la URL de autenticación de tu instalación de Consul Democracy
Te preguntarán por la URL de autenticación de tu instalación de Consul Democracy, y como podrás comprobar corriendo la tarea `rails routes |grep omniauth` en tu repositorio local: Te preguntarán por la URL de autenticación de tu instalación de Consul Democracy, y como podrás comprobar corriendo la tarea `rails routes | grep omniauth` en tu repositorio local:
```bash ```bash
user_twitter_omniauth_authorize GET|POST /users/auth/twitter(.:format) users/omniauth_callbacks#passthru user_twitter_omniauth_authorize GET|POST /users/auth/twitter(.:format) users/omniauth_callbacks#passthru
user_twitter_omniauth_callback GET|POST /users/auth/twitter/callback(.:format) users/omniauth_callbacks#twitter user_twitter_omniauth_callback GET|POST /users/auth/twitter/callback(.:format) users/omniauth_callbacks#twitter
user_facebook_omniauth_authorize GET|POST /users/auth/facebook(.:format) users/omniauth_callbacks#passthru user_facebook_omniauth_authorize GET|POST /users/auth/facebook(.:format) users/omniauth_callbacks#passthru
user_facebook_omniauth_callback GET|POST /users/auth/facebook/callback(.:format) users/omniauth_callbacks#facebook user_facebook_omniauth_callback GET|POST /users/auth/facebook/callback(.:format) users/omniauth_callbacks#facebook
user_google_oauth2_omniauth_authorize GET|POST /users/auth/google_oauth2(.:format) users omniauth_callbacks#passthru user_google_oauth2_omniauth_authorize GET|POST /users/auth/google_oauth2(.:format) users/omniauth_callbacks#passthru
user_google_oauth2_omniauth_callback GET|POST /users/auth/google_oauth2/callback(.:format) users/omniauth_callbacks#google_oauth2 user_google_oauth2_omniauth_callback GET|POST /users/auth/google_oauth2/callback(.:format) users/omniauth_callbacks#google_oauth2
user_wordpress_oauth2_omniauth_authorize GET|POST /users/auth/wordpress_oauth2(.:format) users/omniauth_callbacks#passthru user_wordpress_oauth2_omniauth_authorize GET|POST /users/auth/wordpress_oauth2(.:format) users/omniauth_callbacks#passthru
user_wordpress_oauth2_omniauth_callback GET|POST /users/auth/wordpress_oauth2/callback(.:format) users/omniauth_callbacks#wordpress_oauth2 user_wordpress_oauth2_omniauth_callback GET|POST /users/auth/wordpress_oauth2/callback(.:format) users/omniauth_callbacks#wordpress_oauth2
user_saml_omniauth_authorize GET|POST /users/auth/saml(.:format) users/omniauth_callbacks#passthru
user_saml_omniauth_callback GET|POST /users/auth/saml/callback(.:format) users/omniauth_callbacks#saml
``` ```
Por ejemplo para Facebook la URL sería `yourdomain.com/users/auth/facebook/callback`. Por ejemplo para Facebook la URL sería `yourdomain.com/users/auth/facebook/callback`.
@@ -37,4 +39,7 @@ Cuando completes el registro de la aplicación en su plataforma te darán un *ke
wordpress_oauth2_key: "" wordpress_oauth2_key: ""
wordpress_oauth2_secret: "" wordpress_oauth2_secret: ""
wordpress_oauth2_site: "" wordpress_oauth2_site: ""
saml_sp_entity_id: "https://tusp.org/entityid"
saml_idp_metadata_url: "https://tuidp.org/api/saml/metadata"
saml_idp_sso_service_url: "https://tuidp.org/api/saml/sso"
``` ```

View File

@@ -9,6 +9,7 @@ describe Devise::OmniauthFormComponent do
Setting["feature.twitter_login"] = false Setting["feature.twitter_login"] = false
Setting["feature.google_login"] = false Setting["feature.google_login"] = false
Setting["feature.wordpress_login"] = false Setting["feature.wordpress_login"] = false
Setting["feature.saml_login"] = false
end end
it "is not rendered when all authentications are disabled" do it "is not rendered when all authentications are disabled" do
@@ -52,5 +53,14 @@ describe Devise::OmniauthFormComponent do
expect(page).to have_button "Wordpress" expect(page).to have_button "Wordpress"
expect(page).to have_button count: 1 expect(page).to have_button count: 1
end end
it "renders the SAML link when the feature is enabled" do
Setting["feature.saml_login"] = true
render_inline component
expect(page).to have_button "SAML"
expect(page).to have_button count: 1
end
end end
end end

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

@@ -488,6 +488,163 @@ describe "Users" do
expect(page).to have_field "Email", with: "manuela@consul.dev" expect(page).to have_field "Email", with: "manuela@consul.dev"
end end
end end
context "Saml" do
before { Setting["feature.saml_login"] = true }
let(:saml_hash_with_email) do
{
provider: "saml",
uid: "ext-tester",
info: {
name: "samltester",
email: "tester@consul.dev"
}
}
end
let(:saml_hash_with_verified_email) do
{
provider: "saml",
uid: "ext-tester",
info: {
name: "samltester",
email: "tester@consul.dev",
verified: "1"
}
}
end
scenario "Sign up with a confirmed email" do
OmniAuth.config.add_mock(:saml, saml_hash_with_verified_email)
visit new_user_registration_path
click_button "Sign up with SAML"
expect(page).to have_content "Successfully identified as Saml"
expect_to_be_signed_in
within("#notice") { click_button "Close" }
click_link "My account"
expect(page).to have_field "Username", with: "samltester"
click_link "Change my login details"
expect(page).to have_field "Email", with: "tester@consul.dev"
end
scenario "Sign up with an unconfirmed email" do
OmniAuth.config.add_mock(:saml, saml_hash_with_email)
visit new_user_registration_path
click_button "Sign up with SAML"
expect(page).to have_content "To continue, please click on the confirmation " \
"link that we have sent you via email"
confirm_email
expect(page).to have_content "Your account has been confirmed"
expect(page).to have_current_path new_user_session_path
click_button "Sign in with SAML"
expect(page).to have_content "Successfully identified as Saml"
expect_to_be_signed_in
within("#notice") { click_button "Close" }
click_link "My account"
expect(page).to have_field "Username", with: "samltester"
click_link "Change my login details"
expect(page).to have_field "Email", with: "tester@consul.dev"
end
scenario "Sign in with a user with a SAML identity" do
user = create(:user, username: "samltester", email: "tester@consul.dev", password: "My123456")
create(:identity, uid: "ext-tester", provider: "saml", user: user)
OmniAuth.config.add_mock(:saml, { provider: "saml", uid: "ext-tester" })
visit new_user_session_path
click_button "Sign in with SAML"
expect(page).to have_content "Successfully identified as Saml"
expect_to_be_signed_in
within("#notice") { click_button "Close" }
click_link "My account"
expect(page).to have_field "Username", with: "samltester"
click_link "Change my login details"
expect(page).to have_field "Email", with: "tester@consul.dev"
end
scenario "Sign in with a user without a SAML identity keeps the username" do
create(:user, username: "tester", email: "tester@consul.dev", password: "My123456")
OmniAuth.config.add_mock(:saml, saml_hash_with_verified_email)
visit new_user_session_path
click_button "Sign in with SAML"
expect(page).to have_content "Successfully identified as Saml"
expect_to_be_signed_in
within("#notice") { click_button "Close" }
click_link "My account"
expect(page).to have_field "Username", with: "tester"
click_link "Change my login details"
expect(page).to have_field "Email", with: "tester@consul.dev"
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
scenario "Sign out" do scenario "Sign out" do