Add OIDC section for sign in and sign up page
- name: :oidc → Identifier for this login provider in the app. - scope: [:openid, :email, :profile] → Tells the provider we want the user’s ID (openid), their email, and basic profile info (name, picture, etc.). - response_type: :code → Uses Authorization Code Flow, which is more secure because tokens are not exposed in the URL. - issuer: Rails.application.secrets.oidc_issuer → The base URL of the OIDC provider (e.g., Auth0). Used to find its config. - discovery: true → Automatically fetches the provider’s endpoints from its discovery document instead of manually setting them. - client_auth_method: :basic → Sends client ID and secret using HTTP Basic Auth when exchanging the code for tokens. Add system tests for OIDC Auth Edit the oauth docs to support OIDC auth
This commit is contained in:
committed by
Javi Martín
parent
eab5f52e19
commit
5e263baed2
@@ -1393,7 +1393,8 @@ table {
|
|||||||
.button.button-facebook,
|
.button.button-facebook,
|
||||||
.button.button-google,
|
.button.button-google,
|
||||||
.button.button-wordpress,
|
.button.button-wordpress,
|
||||||
.button.button-saml {
|
.button.button-saml,
|
||||||
|
.button.button-oidc {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
||||||
@@ -1453,6 +1454,16 @@ table {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button.button-oidc {
|
||||||
|
@include has-fa-icon(openid, brands);
|
||||||
|
background: #fdf9f1;
|
||||||
|
border-left: 3px solid #f7931e;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
color: #f7931e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 14. Verification
|
// 14. Verification
|
||||||
// ----------------
|
// ----------------
|
||||||
|
|||||||
@@ -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.oidc_login
|
||||||
feature.saml_login
|
feature.saml_login
|
||||||
feature.twitter_login
|
feature.twitter_login
|
||||||
feature.wordpress_login
|
feature.wordpress_login
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ class Devise::OmniauthFormComponent < ApplicationComponent
|
|||||||
(: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))
|
(:saml if feature?(:saml_login)),
|
||||||
|
(:oidc if feature?(:oidc_login))
|
||||||
].compact
|
].compact
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||||||
sign_in_with :saml_login, :saml
|
sign_in_with :saml_login, :saml
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def oidc
|
||||||
|
sign_in_with :oidc_login, :oidc
|
||||||
|
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
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ module OmniauthTenantSetup
|
|||||||
secrets.saml_idp_metadata_url, secrets.saml_idp_sso_service_url)
|
secrets.saml_idp_metadata_url, secrets.saml_idp_sso_service_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def oidc(env)
|
||||||
|
oidc_auth(env, secrets.oidc_client_id,
|
||||||
|
secrets.oidc_client_secret, secrets.oidc_issuer, secrets.oidc_redirect_uri)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def oauth(env, key, secret)
|
def oauth(env, key, secret)
|
||||||
@@ -55,6 +60,17 @@ module OmniauthTenantSetup
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def oidc_auth(env, client_id, client_secret, issuer, redirect_uri)
|
||||||
|
unless Tenant.default?
|
||||||
|
strategy = env["omniauth.strategy"]
|
||||||
|
|
||||||
|
strategy.options[:client_id] = client_id if client_id.present?
|
||||||
|
strategy.options[:client_secret] = client_secret if client_secret.present?
|
||||||
|
strategy.options[:issuer] = issuer if issuer.present?
|
||||||
|
strategy.options[:redirect_uri] = redirect_uri if redirect_uri.present?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def secrets
|
def secrets
|
||||||
Tenant.current_secrets
|
Tenant.current_secrets
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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.oidc_login": false,
|
||||||
"feature.saml_login": false,
|
"feature.saml_login": false,
|
||||||
"feature.sdg": true,
|
"feature.sdg": true,
|
||||||
"feature.machine_learning": false,
|
"feature.machine_learning": false,
|
||||||
|
|||||||
@@ -296,6 +296,20 @@ Devise.setup do |config|
|
|||||||
end
|
end
|
||||||
config.omniauth :saml, saml_settings.merge(setup: ->(env) { OmniauthTenantSetup.saml(env) })
|
config.omniauth :saml, saml_settings.merge(setup: ->(env) { OmniauthTenantSetup.saml(env) })
|
||||||
|
|
||||||
|
config.omniauth :openid_connect,
|
||||||
|
name: :oidc,
|
||||||
|
scope: [:openid, :email, :profile],
|
||||||
|
response_type: :code,
|
||||||
|
issuer: Rails.application.secrets.oidc_issuer,
|
||||||
|
discovery: true,
|
||||||
|
client_auth_method: :basic,
|
||||||
|
client_options: {
|
||||||
|
identifier: Rails.application.secrets.oidc_client_id,
|
||||||
|
secret: Rails.application.secrets.oidc_client_secret,
|
||||||
|
redirect_uri: Rails.application.secrets.oidc_redirect_uri
|
||||||
|
},
|
||||||
|
setup: ->(env) { OmniauthTenantSetup.oidc(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
|
||||||
# change the failure app, you can configure them inside the config.warden block.
|
# change the failure app, you can configure them inside the config.warden block.
|
||||||
|
|||||||
@@ -282,6 +282,10 @@ en:
|
|||||||
sign_in: Sign in with SAML
|
sign_in: Sign in with SAML
|
||||||
sign_up: Sign up with SAML
|
sign_up: Sign up with SAML
|
||||||
name: SAML
|
name: SAML
|
||||||
|
oidc:
|
||||||
|
sign_in: Sign in with OIDC
|
||||||
|
sign_up: Sign up with OIDC
|
||||||
|
name: OIDC
|
||||||
or_fill: "Or fill the following form:"
|
or_fill: "Or fill the following form:"
|
||||||
proposals:
|
proposals:
|
||||||
create:
|
create:
|
||||||
|
|||||||
@@ -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"
|
||||||
|
oidc_login: "OpenID Connect login"
|
||||||
|
oidc_login_description: "Allow users to sign up with OpenID Connect (OIDC)"
|
||||||
saml_login: "SAML login"
|
saml_login: "SAML login"
|
||||||
saml_login_description: "Allow users to sign up with SAML"
|
saml_login_description: "Allow users to sign up with SAML"
|
||||||
featured_proposals: "Featured proposals"
|
featured_proposals: "Featured proposals"
|
||||||
|
|||||||
@@ -279,6 +279,10 @@ es:
|
|||||||
sign_in: Entra con SAML
|
sign_in: Entra con SAML
|
||||||
sign_up: Regístrate con SAML
|
sign_up: Regístrate con SAML
|
||||||
name: SAML
|
name: SAML
|
||||||
|
oidc:
|
||||||
|
sign_in: Entra con OIDC
|
||||||
|
sign_up: Regístrate con OIDC
|
||||||
|
name: OIDC
|
||||||
info:
|
info:
|
||||||
sign_in: "Entra con:"
|
sign_in: "Entra con:"
|
||||||
sign_up: "Regístrate con:"
|
sign_up: "Regístrate con:"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
oidc_login: "Registro con OpenID Connect"
|
||||||
|
oidc_login_description: "Permitir que los usuarios se registren usando OpenID Connect (OIDC)"
|
||||||
saml_login: "Registro con SAML"
|
saml_login: "Registro con SAML"
|
||||||
saml_login_description: "Permitir que los usuarios se registren usando SAML"
|
saml_login_description: "Permitir que los usuarios se registren usando SAML"
|
||||||
featured_proposals: "Propuestas destacadas"
|
featured_proposals: "Propuestas destacadas"
|
||||||
|
|||||||
@@ -94,6 +94,10 @@ staging:
|
|||||||
saml_sp_entity_id: ""
|
saml_sp_entity_id: ""
|
||||||
saml_idp_metadata_url: ""
|
saml_idp_metadata_url: ""
|
||||||
saml_idp_sso_service_url: ""
|
saml_idp_sso_service_url: ""
|
||||||
|
oidc_client_id: ""
|
||||||
|
oidc_client_secret: ""
|
||||||
|
oidc_issuer: ""
|
||||||
|
oidc_redirect_uri: ""
|
||||||
<<: *maps
|
<<: *maps
|
||||||
<<: *apis
|
<<: *apis
|
||||||
|
|
||||||
@@ -153,6 +157,10 @@ preproduction:
|
|||||||
saml_sp_entity_id: ""
|
saml_sp_entity_id: ""
|
||||||
saml_idp_metadata_url: ""
|
saml_idp_metadata_url: ""
|
||||||
saml_idp_sso_service_url: ""
|
saml_idp_sso_service_url: ""
|
||||||
|
oidc_client_id: ""
|
||||||
|
oidc_client_secret: ""
|
||||||
|
oidc_issuer: ""
|
||||||
|
oidc_redirect_uri: ""
|
||||||
<<: *maps
|
<<: *maps
|
||||||
<<: *apis
|
<<: *apis
|
||||||
|
|
||||||
@@ -211,5 +219,9 @@ production:
|
|||||||
saml_sp_entity_id: ""
|
saml_sp_entity_id: ""
|
||||||
saml_idp_metadata_url: ""
|
saml_idp_metadata_url: ""
|
||||||
saml_idp_sso_service_url: ""
|
saml_idp_sso_service_url: ""
|
||||||
|
oidc_client_id: ""
|
||||||
|
oidc_client_secret: ""
|
||||||
|
oidc_issuer: ""
|
||||||
|
oidc_redirect_uri: ""
|
||||||
<<: *maps
|
<<: *maps
|
||||||
<<: *apis
|
<<: *apis
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# Authentication with external services (OAuth)
|
# Authentication with external services (OAuth)
|
||||||
|
|
||||||
You can configure authentication services with external OAuth providers. Right now, Twitter, Facebook, Google, Wordpress and SAML are supported.
|
You can configure authentication services with external OAuth providers. Right now, Twitter, Facebook, Google, Wordpress, SAML and OpenID Connect (OIDC) are supported.
|
||||||
|
|
||||||
## 1. Create an App on the platform
|
## 1. Create an App on the platform
|
||||||
|
|
||||||
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).
|
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). For OIDC, you'll need to register your application with an OpenID Connect provider.
|
||||||
|
|
||||||
## 2. Set the authentication URL of your Consul Democracy installation
|
## 2. Set the authentication URL of your Consul Democracy installation
|
||||||
|
|
||||||
@@ -21,6 +21,8 @@ user_wordpress_oauth2_omniauth_authorize GET|POST /users/auth/wordpress_oauth2(.
|
|||||||
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_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
|
user_saml_omniauth_callback GET|POST /users/auth/saml/callback(.:format) users/omniauth_callbacks#saml
|
||||||
|
user_oidc_omniauth_authorize GET|POST /users/auth/oidc(.:format) users/omniauth_callbacks#passthru
|
||||||
|
user_oidc_omniauth_callback GET|POST /users/auth/oidc/callback(.:format) users/omniauth_callbacks#oidc
|
||||||
```
|
```
|
||||||
|
|
||||||
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`.
|
||||||
@@ -42,4 +44,8 @@ When you complete the application registration you'll get a *key* and *secret* v
|
|||||||
saml_sp_entity_id: "https://yoursp.org/entityid"
|
saml_sp_entity_id: "https://yoursp.org/entityid"
|
||||||
saml_idp_metadata_url: "https://youridp.org/api/saml/metadata"
|
saml_idp_metadata_url: "https://youridp.org/api/saml/metadata"
|
||||||
saml_idp_sso_service_url: "https://youridp.org/api/saml/sso"
|
saml_idp_sso_service_url: "https://youridp.org/api/saml/sso"
|
||||||
|
oidc_client_id: "your-oidc-client-id"
|
||||||
|
oidc_client_secret: "your-oidc-client-secret"
|
||||||
|
oidc_issuer: "https://your-oidc-provider.com"
|
||||||
|
oidc_redirect_uri: "https://yourapp.com/users/auth/oidc/callback"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# Autenticación con servicios externos (OAuth)
|
# Autenticación con servicios externos (OAuth)
|
||||||
|
|
||||||
Puedes configurar la autenticación con servicios externos usando OAuth. Actualmente, se pueden utilizar Twitter, Facebook, Google, Wordpress y SAML.
|
Puedes configurar la autenticación con servicios externos usando OAuth. Actualmente, se pueden utilizar Twitter, Facebook, Google, Wordpress, SAML y OpenID Connect (OIDC).
|
||||||
|
|
||||||
## 1. Crea una aplicación en la plataforma
|
## 1. Crea una aplicación en la plataforma
|
||||||
|
|
||||||
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).
|
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). Para OIDC, tendrás que registrar tu aplicación con un proveedor de OpenID Connect.
|
||||||
|
|
||||||
## 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
|
||||||
|
|
||||||
@@ -21,6 +21,8 @@ user_wordpress_oauth2_omniauth_authorize GET|POST /users/auth/wordpress_oauth2(.
|
|||||||
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_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
|
user_saml_omniauth_callback GET|POST /users/auth/saml/callback(.:format) users/omniauth_callbacks#saml
|
||||||
|
user_oidc_omniauth_authorize GET|POST /users/auth/oidc(.:format) users/omniauth_callbacks#passthru
|
||||||
|
user_oidc_omniauth_callback GET|POST /users/auth/oidc/callback(.:format) users/omniauth_callbacks#oidc
|
||||||
```
|
```
|
||||||
|
|
||||||
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`.
|
||||||
@@ -42,4 +44,8 @@ Cuando completes el registro de la aplicación en su plataforma te darán un *ke
|
|||||||
saml_sp_entity_id: "https://tusp.org/entityid"
|
saml_sp_entity_id: "https://tusp.org/entityid"
|
||||||
saml_idp_metadata_url: "https://tuidp.org/api/saml/metadata"
|
saml_idp_metadata_url: "https://tuidp.org/api/saml/metadata"
|
||||||
saml_idp_sso_service_url: "https://tuidp.org/api/saml/sso"
|
saml_idp_sso_service_url: "https://tuidp.org/api/saml/sso"
|
||||||
|
oidc_client_id: "tu-id-de-cliente-oidc"
|
||||||
|
oidc_client_secret: "tu-secreto-de-cliente-oidc"
|
||||||
|
oidc_issuer: "https://tu-proveedor-oidc.com"
|
||||||
|
oidc_redirect_uri: "https://tuaplicacion.com/users/auth/oidc/callback"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ describe Devise::OmniauthFormComponent do
|
|||||||
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
|
Setting["feature.saml_login"] = false
|
||||||
|
Setting["feature.oidc_login"] = false
|
||||||
end
|
end
|
||||||
|
|
||||||
it "is not rendered when all authentications are disabled" do
|
it "is not rendered when all authentications are disabled" do
|
||||||
@@ -62,5 +63,14 @@ describe Devise::OmniauthFormComponent do
|
|||||||
expect(page).to have_button "SAML"
|
expect(page).to have_button "SAML"
|
||||||
expect(page).to have_button count: 1
|
expect(page).to have_button count: 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "renders the OIDC link when the feature is enabled" do
|
||||||
|
Setting["feature.oidc_login"] = true
|
||||||
|
|
||||||
|
render_inline component
|
||||||
|
|
||||||
|
expect(page).to have_button "OIDC"
|
||||||
|
expect(page).to have_button count: 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -84,4 +84,96 @@ describe OmniauthTenantSetup do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#oidc" do
|
||||||
|
it "uses different secrets for different tenants" do
|
||||||
|
create(:tenant, schema: "mars")
|
||||||
|
create(:tenant, schema: "venus")
|
||||||
|
|
||||||
|
stub_secrets(
|
||||||
|
oidc_client_id: "default-client-id",
|
||||||
|
oidc_client_secret: "default-client-secret",
|
||||||
|
oidc_issuer: "https://default-oidc.example.com",
|
||||||
|
oidc_redirect_uri: "https://default.consul.dev/auth/oidc/callback",
|
||||||
|
tenants: {
|
||||||
|
mars: {
|
||||||
|
oidc_client_id: "mars-client-id",
|
||||||
|
oidc_client_secret: "mars-client-secret",
|
||||||
|
oidc_issuer: "https://mars-oidc.example.com",
|
||||||
|
oidc_redirect_uri: "https://mars.consul.dev/auth/oidc/callback"
|
||||||
|
},
|
||||||
|
venus: {
|
||||||
|
oidc_client_id: "venus-client-id",
|
||||||
|
oidc_client_secret: "venus-client-secret",
|
||||||
|
oidc_issuer: "https://venus-oidc.example.com",
|
||||||
|
oidc_redirect_uri: "https://venus.consul.dev/auth/oidc/callback"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Tenant.switch("mars") do
|
||||||
|
mars_env = {
|
||||||
|
"omniauth.strategy" => double(options: {}),
|
||||||
|
"HTTP_HOST" => "mars.consul.dev"
|
||||||
|
}
|
||||||
|
|
||||||
|
OmniauthTenantSetup.oidc(mars_env)
|
||||||
|
mars_strategy_options = mars_env["omniauth.strategy"].options
|
||||||
|
|
||||||
|
expect(mars_strategy_options[:client_id]).to eq "mars-client-id"
|
||||||
|
expect(mars_strategy_options[:client_secret]).to eq "mars-client-secret"
|
||||||
|
expect(mars_strategy_options[:issuer]).to eq "https://mars-oidc.example.com"
|
||||||
|
expect(mars_strategy_options[:redirect_uri]).to eq "https://mars.consul.dev/auth/oidc/callback"
|
||||||
|
end
|
||||||
|
|
||||||
|
Tenant.switch("venus") do
|
||||||
|
venus_env = {
|
||||||
|
"omniauth.strategy" => double(options: {}),
|
||||||
|
"HTTP_HOST" => "venus.consul.dev"
|
||||||
|
}
|
||||||
|
|
||||||
|
OmniauthTenantSetup.oidc(venus_env)
|
||||||
|
venus_strategy_options = venus_env["omniauth.strategy"].options
|
||||||
|
|
||||||
|
expect(venus_strategy_options[:client_id]).to eq "venus-client-id"
|
||||||
|
expect(venus_strategy_options[:client_secret]).to eq "venus-client-secret"
|
||||||
|
expect(venus_strategy_options[:issuer]).to eq "https://venus-oidc.example.com"
|
||||||
|
expect(venus_strategy_options[:redirect_uri]).to eq "https://venus.consul.dev/auth/oidc/callback"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "uses default secrets for non-overridden tenant" do
|
||||||
|
create(:tenant, schema: "earth")
|
||||||
|
|
||||||
|
stub_secrets(
|
||||||
|
oidc_client_id: "default-client-id",
|
||||||
|
oidc_client_secret: "default-client-secret",
|
||||||
|
oidc_issuer: "https://default-oidc.example.com",
|
||||||
|
oidc_redirect_uri: "https://default.consul.dev/auth/oidc/callback",
|
||||||
|
tenants: {
|
||||||
|
mars: {
|
||||||
|
oidc_client_id: "mars-client-id",
|
||||||
|
oidc_client_secret: "mars-client-secret",
|
||||||
|
oidc_issuer: "https://mars-oidc.example.com",
|
||||||
|
oidc_redirect_uri: "https://mars.consul.dev/auth/oidc/callback"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Tenant.switch("earth") do
|
||||||
|
earth_env = {
|
||||||
|
"omniauth.strategy" => double(options: {}),
|
||||||
|
"HTTP_HOST" => "earth.consul.dev"
|
||||||
|
}
|
||||||
|
|
||||||
|
OmniauthTenantSetup.oidc(earth_env)
|
||||||
|
earth_strategy_options = earth_env["omniauth.strategy"].options
|
||||||
|
|
||||||
|
expect(earth_strategy_options[:client_id]).to eq "default-client-id"
|
||||||
|
expect(earth_strategy_options[:client_secret]).to eq "default-client-secret"
|
||||||
|
expect(earth_strategy_options[:issuer]).to eq "https://default-oidc.example.com"
|
||||||
|
expect(earth_strategy_options[:redirect_uri]).to eq "https://default.consul.dev/auth/oidc/callback"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -645,6 +645,204 @@ describe "Users" do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "OIDC" do
|
||||||
|
before { Setting["feature.oidc_login"] = true }
|
||||||
|
|
||||||
|
let(:oidc_hash_unverified_email) do
|
||||||
|
{
|
||||||
|
provider: "oidc",
|
||||||
|
uid: "oidc-user-123",
|
||||||
|
info: {
|
||||||
|
name: "oidctester",
|
||||||
|
email: "tester@consul.dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:oidc_hash_with_verified_email) do
|
||||||
|
{
|
||||||
|
provider: "oidc",
|
||||||
|
uid: "oidc-user-123",
|
||||||
|
info: {
|
||||||
|
name: "oidctester",
|
||||||
|
email: "tester@consul.dev",
|
||||||
|
verified: "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario "Sign up with a confirmed email from OIDC provider" do
|
||||||
|
OmniAuth.config.add_mock(:oidc, oidc_hash_with_verified_email)
|
||||||
|
|
||||||
|
visit new_user_registration_path
|
||||||
|
click_button "Sign up with OIDC"
|
||||||
|
|
||||||
|
expect(page).to have_content "Successfully identified as Oidc"
|
||||||
|
expect_to_be_signed_in
|
||||||
|
|
||||||
|
within("#notice") { click_button "Close" }
|
||||||
|
click_link "My account"
|
||||||
|
|
||||||
|
expect(page).to have_field "Username", with: "oidctester"
|
||||||
|
|
||||||
|
click_link "Change my login details"
|
||||||
|
|
||||||
|
expect(page).to have_field "Email", with: "tester@consul.dev"
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario "Sign up with an unconfirmed email from OIDC provider" do
|
||||||
|
OmniAuth.config.add_mock(:oidc, oidc_hash_unverified_email)
|
||||||
|
|
||||||
|
visit new_user_registration_path
|
||||||
|
click_button "Sign up with OIDC"
|
||||||
|
|
||||||
|
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 OIDC"
|
||||||
|
|
||||||
|
expect(page).to have_content "Successfully identified as Oidc"
|
||||||
|
expect_to_be_signed_in
|
||||||
|
|
||||||
|
within("#notice") { click_button "Close" }
|
||||||
|
click_link "My account"
|
||||||
|
|
||||||
|
expect(page).to have_field "Username", with: "oidctester"
|
||||||
|
|
||||||
|
click_link "Change my login details"
|
||||||
|
|
||||||
|
expect(page).to have_field "Email", with: "tester@consul.dev"
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario "Sign in with a user with an OIDC identity" do
|
||||||
|
user = create(:user, username: "oidctester", email: "tester@consul.dev", password: "My123456")
|
||||||
|
create(:identity, uid: "oidc-user-123", provider: "oidc", user: user)
|
||||||
|
OmniAuth.config.add_mock(:oidc, { provider: "oidc", uid: "oidc-user-123" })
|
||||||
|
|
||||||
|
visit new_user_session_path
|
||||||
|
click_button "Sign in with OIDC"
|
||||||
|
|
||||||
|
expect(page).to have_content "Successfully identified as Oidc"
|
||||||
|
expect_to_be_signed_in
|
||||||
|
|
||||||
|
within("#notice") { click_button "Close" }
|
||||||
|
click_link "My account"
|
||||||
|
|
||||||
|
expect(page).to have_field "Username", with: "oidctester"
|
||||||
|
|
||||||
|
click_link "Change my login details"
|
||||||
|
|
||||||
|
expect(page).to have_field "Email", with: "tester@consul.dev"
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario "Sign in with a user without an OIDC identity keeps the username" do
|
||||||
|
create(:user, username: "tester", email: "tester@consul.dev", password: "My123456")
|
||||||
|
OmniAuth.config.add_mock(:oidc, oidc_hash_with_verified_email)
|
||||||
|
|
||||||
|
visit new_user_session_path
|
||||||
|
click_button "Sign in with OIDC"
|
||||||
|
|
||||||
|
expect(page).to have_content "Successfully identified as Oidc"
|
||||||
|
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 "OIDC 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.oidc_login"] = true }
|
||||||
|
end
|
||||||
|
|
||||||
|
Tenant.switch("mars") do
|
||||||
|
mars_user = create(:user, username: "marsuser", email: "mars@consul.dev")
|
||||||
|
create(:identity, uid: "mars-oidc-123", provider: "oidc", user: mars_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
mars_oidc_hash = {
|
||||||
|
provider: "oidc",
|
||||||
|
uid: "mars-oidc-123",
|
||||||
|
info: {
|
||||||
|
name: "marsuser",
|
||||||
|
email: "mars@consul.dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OmniAuth.config.add_mock(:oidc, mars_oidc_hash)
|
||||||
|
|
||||||
|
with_subdomain("mars") do
|
||||||
|
visit new_user_session_path
|
||||||
|
click_button "Sign in with OIDC"
|
||||||
|
|
||||||
|
expect(page).to have_content "Successfully identified as Oidc"
|
||||||
|
|
||||||
|
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 OIDC"
|
||||||
|
|
||||||
|
expect(page).to have_content "To continue, please click on the confirmation " \
|
||||||
|
"link that we have sent you via email"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario "Allows user authentication when OIDC token has expired" do
|
||||||
|
user = create(:user, username: "oidctester", email: "tester@consul.dev")
|
||||||
|
create(:identity, uid: "oidc-user-123", provider: "oidc", user: user)
|
||||||
|
|
||||||
|
expired_oidc_hash = oidc_hash_with_verified_email.merge(
|
||||||
|
credentials: {
|
||||||
|
token: "expired_token",
|
||||||
|
expires_at: 1.hour.ago.to_i,
|
||||||
|
expires: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
OmniAuth.config.add_mock(:oidc, expired_oidc_hash)
|
||||||
|
|
||||||
|
visit new_user_session_path
|
||||||
|
click_button "Sign in with OIDC"
|
||||||
|
|
||||||
|
expect(page).to have_content "Successfully identified as Oidc"
|
||||||
|
expect_to_be_signed_in
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario "Handle missing email claim from OIDC provider" do
|
||||||
|
oidc_hash_no_email = {
|
||||||
|
provider: "oidc",
|
||||||
|
uid: "oidc-user-no-email",
|
||||||
|
info: {
|
||||||
|
name: "noemailuser"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OmniAuth.config.add_mock(:oidc, oidc_hash_no_email)
|
||||||
|
|
||||||
|
visit new_user_registration_path
|
||||||
|
click_button "Sign up with OIDC"
|
||||||
|
|
||||||
|
expect(page).to have_content "1 error prevented this Account from being saved."
|
||||||
|
|
||||||
|
expect(page).to have_content "can't be blank"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
scenario "Sign out" do
|
scenario "Sign out" do
|
||||||
|
|||||||
Reference in New Issue
Block a user