diff --git a/Gemfile b/Gemfile index acc2c0e..5f8d5d8 100644 --- a/Gemfile +++ b/Gemfile @@ -37,6 +37,7 @@ gem "decidim-survey_multiple_answers", git: "https://github.com/OpenSourcePoliti gem "decidim-term_customizer", git: "https://github.com/OpenSourcePolitics/decidim-module-term_customizer.git", branch: "fix/email_with_precompile" # Omniauth gems +gem "omniauth-cas" gem "omniauth-france_connect", git: "https://github.com/OpenSourcePolitics/omniauth-france_connect" gem "omniauth_openid_connect" gem "omniauth-publik", git: "https://github.com/OpenSourcePolitics/omniauth-publik" diff --git a/Gemfile.lock b/Gemfile.lock index c7d6e48..c8089a9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -796,6 +796,10 @@ GEM hashie (>= 3.4.6) rack (>= 2.2.3) rack-protection + omniauth-cas (3.0.0) + addressable (~> 2.8) + nokogiri (~> 1.12) + omniauth (~> 2.1) omniauth-facebook (5.0.0) omniauth-oauth2 (~> 1.2) omniauth-google-oauth2 (1.1.2) @@ -1187,6 +1191,7 @@ DEPENDENCIES lograge multipart-post nokogiri (= 1.13.4) + omniauth-cas omniauth-france_connect! omniauth-publik! omniauth-rails_csrf_protection (~> 1.0) diff --git a/app/packs/images/icon-cas.jpeg b/app/packs/images/icon-cas.jpeg new file mode 100644 index 0000000..0e36949 Binary files /dev/null and b/app/packs/images/icon-cas.jpeg differ diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 1425f5c..467e4d3 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -129,6 +129,8 @@ ignore_unused: - decidim.authorization_handlers.osp_authorization_handler.{explanation, name} - decidim.authorization_handlers.osp_authorization_handler.fields.* - decidim.authorization_handlers.osp_authorization_workflow.name + - decidim.authorization_handlers.admin.* + - decidim.authorization_handlers.{cas, cas_student}.* - decidim.events.budgets.pending_order.* - decidim.events.users.user_officialized.* - decidim.events.verifications.verify_with_managed_user.* @@ -140,6 +142,7 @@ ignore_unused: - decidim.system.organizations.omniauth_settings.{france_connect, france_connect_profile, france_connect_uid}.* - decidim.system.organizations.omniauth_settings.openid_connect.* - decidim.system.organizations.omniauth_settings.publik.* + - decidim.system.organizations.omniauth_settings.{cas, cas_student}.* - decidim.verifications.authorizations.create.* - decidim.verifications.authorizations.first_login.actions.* - rack_attack.too_many_requests.* diff --git a/config/initializers/omniauth_cas.rb b/config/initializers/omniauth_cas.rb new file mode 100644 index 0000000..bead0c8 --- /dev/null +++ b/config/initializers/omniauth_cas.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "omniauth/strategies/ubx" + +Rails.application.config.middleware.use OmniAuth::Builder do + OmniAuth.config.logger = Rails.logger + + omniauth_config = Rails.application.secrets.fetch(:omniauth, {}).with_indifferent_access + + if omniauth_config[:cas].present? + provider( + OmniAuth::Strategies::UBX, + setup: lambda { |env| + request = Rack::Request.new(env) + organization = env["decidim.current_organization"].presence || Decidim::Organization.find_by(host: request.host) + provider_config = organization.enabled_omniauth_providers[:cas] || {} + + env["omniauth.strategy"].options[:host] = provider_config[:host] || omniauth_config.dig(:cas, :host) + env["omniauth.strategy"].options[:ssl] = provider_config[:ssl] || omniauth_config.dig(:cas, :ssl) + } + ) + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index 05468ab..6ef9b45 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -30,6 +30,23 @@ en: is in evaluation state. anonymous_user: Anonymous user authorization_handlers: + admin: + cas: + help: + - Validate with an external CAS account + cas_student: + help: + - Validate with an external IDNUM student account + cas: + explanation: Validate with an external IDNUM account + fields: + status: status + name: IDNUM + cas_student: + explanation: Validate with an external IDNUM student account + fields: + status: status + name: IDNUM student osp_authorization_handler: explanation: Verify your identity by entering a unique number fields: @@ -153,6 +170,10 @@ en: system: organizations: omniauth_settings: + cas: + host: External service host (without http(s)://) + provider_name: External service name + ssl: Enable SSL (true|false) france_connect: client_id: Client ID client_secret: Client secret @@ -193,6 +214,8 @@ en: success: Success first_login: actions: + cas: Verify your identity with an IDNUM account + cas_student: Verify your identity with an IDNUM student account osp_authorization_handler: Verify with the identity verification form osp_authorization_workflow: Verify with the identity verification form devise: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 5e08e14..b781b16 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -32,6 +32,23 @@ fr: est en cours d’évaluation. anonymous_user: Utilisateur anonyme authorization_handlers: + admin: + cas: + help: + - Confirmer une identité avec un compte IDNUM + cas_student: + help: + - Confirmer une identité avec un compte IDNUM étudiant + cas: + explanation: Confirmer votre identité avec un compte IDNUM + fields: + status: votre statut + name: IDNUM + cas_student: + explanation: Confirmer votre identité avec un compte IDNUM + fields: + status: votre statut + name: IDNUM étudiant osp_authorization_handler: explanation: Vérifier votre identité en saisissant un numéro unique fields: @@ -155,6 +172,10 @@ fr: system: organizations: omniauth_settings: + cas: + host: Hôte du serveur distant (sans http(s)://) + provider_name: Nom du service distant + ssl: Activer le SSL (true|false) france_connect: client_id: Client ID client_secret: Client secret @@ -195,6 +216,8 @@ fr: success: Vous avez été vérifié avec succès. first_login: actions: + cas: Confirmer votre identité avec un compte IDNUM + cas_student: Confirmer votre identité avec un compte IDNUM étudiant osp_authorization_handler: Vérifier avec le formulaire de vérification de l'identité osp_authorization_workflow: Vérifier avec le formulaire de vérification de l'identité devise: diff --git a/config/secrets.yml b/config/secrets.yml index e894445..a447755 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -62,6 +62,12 @@ default: &default main: <%= ENV["HELP_SCOUT_BEACON_ID_MAIN"] %> fallback: <%= ENV["HELP_SCOUT_BEACON_ID_FALLBACK"] %> omniauth: + cas: + enabled: false + icon_path: "cas-icon.svg" + provider_name: "IDNUM" + host: <%= ENV["OMNIAUTH_SAML_HOST"] %> + ssl: true facebook: # It must be a boolean. Remember ENV variables doesn't support booleans. enabled: false diff --git a/lib/omniauth/strategies/ubx.rb b/lib/omniauth/strategies/ubx.rb new file mode 100644 index 0000000..61d08a0 --- /dev/null +++ b/lib/omniauth/strategies/ubx.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "omniauth-cas" + +module OmniAuth + module Strategies + class UBX < OmniAuth::Strategies::CAS + option :name, :cas + option :origin_param, "redirect_url" + option :service_validate_url, "/p3/serviceValidate" + + option :first_name_key, "sn" + option :last_name_key, "givenName" + option :email_key, "mail" + option :status_key, "eduPersonEntitlement" + + # Auth hash schema keys for consistency with OmniAuth schema + AUTH_HASH_SCHEMA_KEYS = %w(name email nickname first_name last_name location image phone status).freeze + + info do + prune!( + name: "#{raw_info[options[:first_name_key].to_s]} #{raw_info[options[:last_name_key].to_s]}", + email: raw_info[options[:email_key].to_s], + nickname: raw_info[options[:nickname_key].to_s], + first_name: raw_info[options[:first_name_key].to_s], + last_name: raw_info[options[:last_name_key].to_s], + location: raw_info[options[:location_key].to_s], + image: raw_info[options[:image_key].to_s], + phone: raw_info[options[:phone_key].to_s], + status: raw_info[options[:status_key].to_s] + ) + end + + private + + def prune!(hash) + hash.delete_if { |_key, value| value.blank? } + end + end + end +end + +OmniAuth.config.add_camelization("cas", "CAS")