diff --git a/Gemfile.lock b/Gemfile.lock index 9d9bfd0d..fb95fba8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -15,7 +15,7 @@ GIT GIT remote: https://github.com/OpenSourcePolitics/omniauth-france_connect - revision: 4665875c94d45a71dac163889e0eedd21b2ba41d + revision: 14a53ad31928c8a83742360cfbdb90938d0a057e specs: omniauth-france_connect (0.1.0) omniauth_openid_connect (~> 0.4.0) diff --git a/OVERLOADS.md b/OVERLOADS.md index a64b3b68..27aec3fb 100644 --- a/OVERLOADS.md +++ b/OVERLOADS.md @@ -1,5 +1,9 @@ # Overrides +## Update France Connect with requirements +* `app/views/decidim/devise/passwords/new.html.erb` +* `app/views/decidim/shared/_login_modal.html.erb` + ## Load decidim-awesome assets only if dependencie is present * `app/views/layouts/decidim/_head.html.erb:33` diff --git a/app/packs/images/FCboutons-10.png b/app/packs/images/FCboutons-10.png index bb5adf7b..5a179f87 100644 Binary files a/app/packs/images/FCboutons-10.png and b/app/packs/images/FCboutons-10.png differ diff --git a/app/packs/images/FCboutons-10@2x.png b/app/packs/images/FCboutons-10@2x.png index c7461283..9668728b 100644 Binary files a/app/packs/images/FCboutons-10@2x.png and b/app/packs/images/FCboutons-10@2x.png differ diff --git a/app/views/decidim/devise/passwords/new.html.erb b/app/views/decidim/devise/passwords/new.html.erb new file mode 100644 index 00000000..ebf006d7 --- /dev/null +++ b/app/views/decidim/devise/passwords/new.html.erb @@ -0,0 +1,37 @@ +<% add_decidim_page_title(t("devise.passwords.new.forgot_your_password")) %> + +
+
+
+
+

<%= t("devise.passwords.new.forgot_your_password") %>

+
+
+ +
+
+
+
+ <%= decidim_form_for(resource, namespace: "password", as: resource_name, url: password_path(resource_name), html: { method: :post, class: "register-form new_user" }) do |f| %> +
+ <%= f.email_field :email, autofocus: true %> +
+ + <% if current_organization.enabled_omniauth_providers.keys.any? { |provider| provider.match?("france") } %> +
+

<%= t("decidim.omniauth.france_connect.forgot_password.ok_text") %>

+
+ <% end %> + +
+ <%= f.submit t("devise.passwords.new.send_me_reset_password_instructions"), class: "button expanded" %> +
+ + <% end %> + <%= render "decidim/devise/shared/links" %> +
+
+
+
+
+
\ No newline at end of file diff --git a/app/views/decidim/devise/shared/_omniauth_buttons.html.erb b/app/views/decidim/devise/shared/_omniauth_buttons.html.erb index 53609a89..a181ed6a 100644 --- a/app/views/decidim/devise/shared/_omniauth_buttons.html.erb +++ b/app/views/decidim/devise/shared/_omniauth_buttons.html.erb @@ -1,23 +1,28 @@ <% if Devise.mappings[:user].omniauthable? && current_organization.enabled_omniauth_providers.any? %>
-
+
<%- current_organization.enabled_omniauth_providers.keys.each do |provider| %> <% end %> @@ -28,4 +33,4 @@ <%- end %>
-<% end %> +<% end %> \ No newline at end of file diff --git a/app/views/decidim/shared/_login_modal.html.erb b/app/views/decidim/shared/_login_modal.html.erb new file mode 100644 index 00000000..ddd14236 --- /dev/null +++ b/app/views/decidim/shared/_login_modal.html.erb @@ -0,0 +1,60 @@ + \ No newline at end of file diff --git a/config/initializers/extends.rb b/config/initializers/extends.rb index 01c10eab..eb1d5bc1 100644 --- a/config/initializers/extends.rb +++ b/config/initializers/extends.rb @@ -2,3 +2,4 @@ require "extends/controllers/decidim/devise/sessions_controller_extends" require "extends/queries/decidim/participatory_processes/group_participatory_processes_extends" +require "extends/controllers/decidim/devise/account_controller_extends" diff --git a/config/initializers/omniauth_france_connect.rb b/config/initializers/omniauth_france_connect.rb index f0caf0a4..f450eb37 100644 --- a/config/initializers/omniauth_france_connect.rb +++ b/config/initializers/omniauth_france_connect.rb @@ -1,18 +1,16 @@ # frozen_string_literal: true -if Rails.application.secrets.dig(:omniauth, :france_connect).present? - Rails.application.config.middleware.use OmniAuth::Builder do - provider( - :france_connect, - setup: lambda { |env| - request = Rack::Request.new(env) - organization = Decidim::Organization.find_by(host: request.host) - provider_config = organization.enabled_omniauth_providers[:france_connect] - env["omniauth.strategy"].options[:client_id] = provider_config[:client_id] - env["omniauth.strategy"].options[:client_secret] = provider_config[:client_secret] - env["omniauth.strategy"].options[:site] = provider_config[:site_url] - env["omniauth.strategy"].options[:scope] = provider_config[:scope]&.split(" ") - } - ) - end +Rails.application.config.middleware.use OmniAuth::Builder do + provider( + :france_connect, + 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[:france_connect] + env["omniauth.strategy"].options[:client_id] = provider_config[:client_id] + env["omniauth.strategy"].options[:client_secret] = provider_config[:client_secret] + env["omniauth.strategy"].options[:site] = provider_config[:site_url] + env["omniauth.strategy"].options[:scope] = provider_config[:scope]&.split(" ") + } + ) end diff --git a/config/locales/en.yml b/config/locales/en.yml index 71bf7942..b713c9db 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -9,11 +9,6 @@ en: participatory_process: private_space: Private space decidim: - omniauth: - france_connect: - external: - link: https://franceconnect.gouv.fr/ - text: Qu'est-ce-que FranceConnect ? accessibility: skip_button: Skip button admin: @@ -37,6 +32,10 @@ en: name: Identity Verification Form osp_authorization_workflow: name: Authorization procedure + devise: + sessions: + new: + sign_in_disabled: Sign in disabled events: budgets: pending_order: @@ -57,11 +56,7 @@ en: email_subject: Failed verification attempt against a managed participant notification_title: The participant %{resource_title} has tried to verify themself with the data of the managed participant %{managed_user_name}. newsletter_templates: - mel_template_settings_form: - interpolations_hint: Interpolations hint - body: Body mel_template: - name: Template alt_banner_image: Alt banner image mel_template_settings_form: show: @@ -79,6 +74,17 @@ en: icon: Icon link: Link text: Text + name: Template + mel_template_settings_form: + body: Body + interpolations_hint: Interpolations hint + omniauth: + france_connect: + external: + link: https://franceconnect.gouv.fr/ + text: What is FranceConnect ? + forgot_password: + ok_text: Warning, this password is the one of your local account and in no case the one of the account you use through FranceConnect. It will only be used when you log in with your email address rather than via FranceConnect. proposals: create: error: There was a problem saving @@ -101,6 +107,11 @@ en: withdraw: errors: has_supports: This proposal can not be withdrawn because it already has supports. + shared: + login_modal: + close_modal: Fermer + please_sign_in: Veuillez vous connecter + sign_up: Créer un compte system: organizations: omniauth_settings: @@ -133,6 +144,17 @@ en: actions: osp_authorization_handler: Verify with the identity verification form osp_authorization_workflow: Verify with the identity verification form + devise: + passwords: + new: + forgot_your_password: Forgot your password + send_me_reset_password_instructions: Send me reset password instructions + sessions: + new: + sign_in: Log in + shared: + links: + forgot_your_password: Forgot your password faker: address: country_code: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index e081ee43..a8428761 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -9,11 +9,6 @@ fr: participatory_process: private_space: Espace privé decidim: - omniauth: - france_connect: - external: - link: https://franceconnect.gouv.fr/ - text: Qu'est-ce-que FranceConnect ? accessibility: skip_button: Passer au contenu principal admin: @@ -37,6 +32,10 @@ fr: name: Formulaire de vérification d'identité osp_authorization_workflow: name: Procédure d'autorisation + devise: + sessions: + new: + sign_in_disabled: Vous pouvez accéder avec un compte externe events: budgets: pending_order: @@ -77,6 +76,13 @@ fr: show: body: Contenu principal interpolations_hint: Interpolation + omniauth: + france_connect: + external: + link: https://franceconnect.gouv.fr/ + text: Qu'est-ce-que FranceConnect ? + forgot_password: + ok_text: Warning, this password is the one of your local account and in no case the one of the account you use through FranceConnect. It will only be used when you log in with your email address rather than via FranceConnect. proposals: create: error: Il y a eu des erreurs lors de la sauvegarde de la proposition. @@ -99,15 +105,20 @@ fr: withdraw: errors: has_supports: Cette proposition ne peut pas être retirée car elle dispose déjà de supports. + shared: + login_modal: + close_modal: Close modal + please_sign_in: Please sign in + sign_up: Sign up system: organizations: omniauth_settings: france_connect: client_id: Client ID client_secret: Client secret - scope: Périmètre de données provider: FranceConnect provider_name: FranceConnect + scope: Périmètre de données site_url: Site URL france_connect_profile: button_path: Chemin du bouton @@ -131,6 +142,17 @@ fr: actions: 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: + passwords: + new: + forgot_your_password: Mot de passe oublié ? + send_me_reset_password_instructions: Envoyez-moi les instructions de réinitialisation du mot de passe + sessions: + new: + sign_in: S'identifier + shared: + links: + forgot_your_password: Mot de passe oublié ? faker: address: country_code: diff --git a/lib/extends/controllers/decidim/devise/account_controller_extends.rb b/lib/extends/controllers/decidim/devise/account_controller_extends.rb new file mode 100644 index 00000000..de4a8d98 --- /dev/null +++ b/lib/extends/controllers/decidim/devise/account_controller_extends.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module AccountControllerExtends + def destroy + enforce_permission_to :delete, :user, current_user: current_user + @form = form(Decidim::DeleteAccountForm).from_params(params) + + Decidim::DestroyAccount.call(current_user, @form) do + on(:ok) do + sign_out(current_user) + if active_france_connect_session? + destroy_france_connect_session(session["omniauth.france_connect.end_session_uri"]) + else + redirect_to decidim.root_path + end + flash[:notice] = t("account.destroy.success", scope: "decidim") + end + + on(:invalid) do + flash[:alert] = t("account.destroy.error", scope: "decidim") + redirect_to decidim.root_path + end + end + end + + private + + def destroy_france_connect_session(fc_logout_path) + session.delete("omniauth.france_connect.end_session_uri") + + redirect_to fc_logout_path + end + + def active_france_connect_session? + current_organization.enabled_omniauth_providers.include?(:france_connect) && session["omniauth.france_connect.end_session_uri"].present? + end +end + +Decidim::AccountController.class_eval do + prepend(AccountControllerExtends) +end diff --git a/lib/extends/controllers/decidim/devise/sessions_controller_extends.rb b/lib/extends/controllers/decidim/devise/sessions_controller_extends.rb index 38166b22..deaa01dd 100644 --- a/lib/extends/controllers/decidim/devise/sessions_controller_extends.rb +++ b/lib/extends/controllers/decidim/devise/sessions_controller_extends.rb @@ -1,6 +1,17 @@ # frozen_string_literal: true module SessionControllerExtends + def destroy + current_user.invalidate_all_sessions! + if active_france_connect_session? + destroy_france_connect_session(session["omniauth.france_connect.end_session_uri"]) + elsif params[:translation_suffix].present? + super { set_flash_message! :notice, params[:translation_suffix], { scope: "decidim.devise.sessions" } } + else + super + end + end + def after_sign_in_path_for(user) return super if user.is_a? Decidim::System::Admin @@ -19,6 +30,20 @@ def after_sign_in_path_for(user) def skip_authorization_handler? ENV["SKIP_FIRST_LOGIN_AUTHORIZATION"] ? ActiveRecord::Type::Boolean.new.cast(ENV["SKIP_FIRST_LOGIN_AUTHORIZATION"]) : true end + + def destroy_france_connect_session(fc_logout_path) + signed_out = (::Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)) + if signed_out + set_flash_message! :notice, :signed_out + session.delete("omniauth.france_connect.end_session_uri") + end + + redirect_to fc_logout_path + end + + def active_france_connect_session? + current_organization.enabled_omniauth_providers.include?(:france_connect) && session["omniauth.france_connect.end_session_uri"].present? + end end Devise::SessionsController.class_eval do diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index 9f34bc3d..4da6dc37 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -97,10 +97,11 @@ module Devise end describe "DELETE destroy" do - let(:user) { create(:user, :confirmed) } + let(:organization) { create(:organization) } + let(:user) { create(:user, :confirmed, organization: organization) } before do - request.env["decidim.current_organization"] = user.organization + request.env["decidim.current_organization"] = organization request.env["devise.mapping"] = ::Devise.mappings[:user] sign_in user @@ -111,6 +112,43 @@ module Devise expect(controller.current_user).to be_nil end + + context "when France Connect is enabled" do + let(:organization) { create(:organization, omniauth_settings: omniauth_settings) } + let(:omniauth_settings) do + { "omniauth_settings_france_connect_enabled": true } + end + + before do + stub_request(:get, /test-france-connect.fr/) + .with(headers: { "Accept" => "*/*", "User-Agent" => "Ruby" }) + .to_return(status: 200, body: "", headers: {}) + + request.env["decidim.current_organization"] = user.organization + request.env["devise.mapping"] = ::Devise.mappings[:user] + + sign_in user + end + + it "logout user from France Connect" do + delete :destroy, session: { "omniauth.france_connect.end_session_uri" => "http://test-france-connect.fr/" } + + expect(controller.current_user).to be_nil + expect(controller).to redirect_to("http://test-france-connect.fr/") + expect(session["flash"]["flashes"]["notice"]).to eq("Signed out successfully.") + end + + context "and France Connect logout session is not present" do + it "logout user from application" do + delete :destroy + + expect(controller.current_user).to be_nil + expect(controller).not_to redirect_to("http://test-france-connect.fr/") + expect(controller).to redirect_to("http://test.host/") + expect(session["flash"]["flashes"]["notice"]).to eq("Signed out successfully.") + end + end + end end end end