From a934188bdfddfd772f8c46d93bdc8140f9fa054d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Charignon?= Date: Wed, 11 Sep 2024 09:46:56 +0200 Subject: [PATCH] Remplace InclusionConnect par ProConnect --- .../inclusion_connect_controller.rb | 42 ----------- app/controllers/pro_connect_controller.rb | 49 +++++++++++++ ...onnect_helper.rb => pro_connect_helper.rb} | 69 +++++++++++-------- .../active_admin/devise/sessions/new.html.erb | 4 +- config/initializers/active_admin.rb | 2 +- config/locales/inclusion_connect.yml | 3 - config/locales/pro_connect.yml | 3 + config/routes.rb | 6 +- ...per_spec.rb => pro_connect_helper_spec.rb} | 50 +++++++------- 9 files changed, 123 insertions(+), 105 deletions(-) delete mode 100644 app/controllers/inclusion_connect_controller.rb create mode 100644 app/controllers/pro_connect_controller.rb rename app/helpers/{inclusion_connect_helper.rb => pro_connect_helper.rb} (55%) delete mode 100644 config/locales/inclusion_connect.yml create mode 100644 config/locales/pro_connect.yml rename spec/helpers/{inclusion_connect_helper_spec.rb => pro_connect_helper_spec.rb} (82%) diff --git a/app/controllers/inclusion_connect_controller.rb b/app/controllers/inclusion_connect_controller.rb deleted file mode 100644 index 78e083317..000000000 --- a/app/controllers/inclusion_connect_controller.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -class InclusionConnectController < ApplicationController - def auth - session[:ic_state] = Digest::SHA1.hexdigest("InclusionConnect - #{SecureRandom.hex(13)}") - redirect_to InclusionConnectHelper.auth_path(session[:ic_state], - inclusion_connect_callback_url), - allow_other_host: true - end - - def callback - tokens = correct_state? && - InclusionConnectHelper.recupere_tokens(params[:code], inclusion_connect_callback_url) - compte = tokens && InclusionConnectHelper.compte(tokens['access_token']) - echec_login and return if compte.blank? - - session[:ic_logout_token] = tokens['id_token'] - sign_in_and_redirect compte, scope: :compte - end - - def logout - if session[:ic_state].blank? || session[:ic_logout_token].blank? - redirect_to destroy_compte_session_url and return - end - - sign_out current_compte - redirect_to InclusionConnectHelper.logout(session, destroy_compte_session_url), - allow_other_host: true - end - - private - - def echec_login - flash[:error] = I18n.t('.inclusion-connect.authentification-impossible', - email_contact: Eva::EMAIL_SUPPORT) - redirect_to new_compte_session_path - end - - def correct_state? - params[:state] == session[:ic_state] - end -end diff --git a/app/controllers/pro_connect_controller.rb b/app/controllers/pro_connect_controller.rb new file mode 100644 index 000000000..112fca311 --- /dev/null +++ b/app/controllers/pro_connect_controller.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +class ProConnectController < ApplicationController + def auth + session[:pc_state] = Digest::SHA256.hexdigest("ProConnect - #{SecureRandom.hex(32)}") + session[:pc_nonce] = Digest::SHA256.hexdigest("ProConnect - #{SecureRandom.hex(32)}") + redirect_to ProConnectHelper.auth_path(session[:pc_state], session[:pc_nonce], + pro_connect_callback_url), + allow_other_host: true + end + + def callback + tokens = correct_state? && + ProConnectHelper.recupere_tokens(params[:code], pro_connect_callback_url) + compte = lit_tokens(tokens) + echec_login and return if compte.blank? + + session[:pc_logout_token] = tokens['id_token'] + sign_in_and_redirect compte, scope: :compte + end + + def lit_tokens(tokens) + tokens && + ProConnectHelper.verifie(tokens['id_token'], session[:pc_nonce]) && + ProConnectHelper.compte(tokens['access_token']) + end + + def logout + if session[:pc_state].blank? || session[:pc_logout_token].blank? + redirect_to destroy_compte_session_url and return + end + + sign_out current_compte + redirect_to ProConnectHelper.logout(session, destroy_compte_session_url), + allow_other_host: true + end + + private + + def echec_login + flash[:error] = I18n.t('.pro-connect.authentification-impossible', + email_contact: Eva::EMAIL_SUPPORT) + redirect_to new_compte_session_path + end + + def correct_state? + params[:state] == session[:pc_state] + end +end diff --git a/app/helpers/inclusion_connect_helper.rb b/app/helpers/pro_connect_helper.rb similarity index 55% rename from app/helpers/inclusion_connect_helper.rb rename to app/helpers/pro_connect_helper.rb index 0f204a677..067e2b13d 100644 --- a/app/helpers/inclusion_connect_helper.rb +++ b/app/helpers/pro_connect_helper.rb @@ -1,46 +1,58 @@ # frozen_string_literal: true -module InclusionConnectHelper - IC_CLIENT_ID = ENV.fetch('INCLUSION_CONNECT_CLIENT_ID', nil) - IC_CLIENT_SECRET = ENV.fetch('INCLUSION_CONNECT_CLIENT_SECRET', nil) - IC_BASE_URL = ENV.fetch('INCLUSION_CONNECT_BASE_URL', nil) +module ProConnectHelper + PC_CLIENT_ID = ENV.fetch('PRO_CONNECT_CLIENT_ID', nil) + PC_CLIENT_SECRET = ENV.fetch('PRO_CONNECT_CLIENT_SECRET', nil) + PC_BASE_URL = ENV.fetch('PRO_CONNECT_BASE_URL', nil) class << self - def auth_path(ic_state, callback_url) + def auth_path(pc_state, pc_nonce, callback_url) query = { response_type: 'code', - client_id: IC_CLIENT_ID, + client_id: PC_CLIENT_ID, redirect_uri: callback_url, - scope: 'openid email profile', - state: ic_state, - nonce: Digest::SHA1.hexdigest('Something to check when it come back ?'), - from: 'community' + acr_values: 'eidas1', + scope: 'openid email usual_name given_name', + state: pc_state, + nonce: pc_nonce } - "#{IC_BASE_URL}/auth/authorize?#{query.to_query}" + "#{PC_BASE_URL}/api/v2/authorize?#{query.to_query}" end def logout_confirmed(post_logout_redirect_uri) query = { - client_id: IC_CLIENT_ID, + client_id: PC_CLIENT_ID, post_logout_redirect_uri: post_logout_redirect_uri } - "#{IC_BASE_URL}/auth/logout?#{query.to_query}" + "#{PC_BASE_URL}/api/v2/session/end?#{query.to_query}" end def logout(session, post_logout_redirect_uri) query = { - state: session[:ic_state], - id_token_hint: session[:ic_logout_token], + state: session[:pc_state], + id_token_hint: session[:pc_logout_token], post_logout_redirect_uri: post_logout_redirect_uri } - session[:ic_logout_token] = nil - "#{IC_BASE_URL}/auth/logout?#{query.to_query}" + session[:pc_logout_token] = nil + "#{PC_BASE_URL}/api/v2/session/end?#{query.to_query}" end - def compte(token) - return false if token.blank? + def decode_jwt(jwt) + decode = JWT.decode(jwt, PC_CLIENT_SECRET, true, { algorithm: 'HS256' }) + yield(decode[0]) + rescue JWT::DecodeError => e + Rails.logger.warn "erreur JWT: #{e}" + false + end + + def verifie(id_token_jwt, nonce) + decode_jwt(id_token_jwt) { |id_token| id_token['nonce'] == nonce } + end - user_info = get_user_info(token) + def compte(access_token) + return false if access_token.blank? + + user_info = get_user_info(access_token) return false if user_info.blank? cree_ou_recupere_compte(user_info) @@ -48,11 +60,11 @@ def compte(token) def recupere_tokens(code, callback_url) data = { grant_type: 'authorization_code', - client_id: IC_CLIENT_ID, client_secret: IC_CLIENT_SECRET, + client_id: PC_CLIENT_ID, client_secret: PC_CLIENT_SECRET, code: code, redirect_uri: callback_url } res = Typhoeus.post( - URI("#{IC_BASE_URL}/auth/token/"), + URI("#{PC_BASE_URL}/api/v2/token"), body: data.to_query, headers: { 'Content-Type' => 'application/x-www-form-urlencoded' } ) @@ -62,18 +74,17 @@ def recupere_tokens(code, callback_url) end def get_user_info(token) - uri = URI("#{IC_BASE_URL}/auth/userinfo/") - uri.query = URI.encode_www_form({ schema: 'openid' }) + uri = URI("#{PC_BASE_URL}/api/v2/userinfo") res = Typhoeus.get(uri, headers: { 'Authorization' => "Bearer #{token}" }) return false unless res.success? - JSON.parse(res.body) + decode_jwt(res.body) { |user_info| user_info } end def cree_ou_recupere_compte(user_info) email = user_info['email'].strip.downcase - compte = Compte.find_by(id_inclusion_connect: user_info['sub']) + compte = Compte.find_by(id_pro_connect: user_info['sub']) if compte.present? && compte.email != email compte = actualise_email_compte_existant(compte, email) end @@ -88,7 +99,7 @@ def cree_ou_recupere_compte(user_info) def actualise_email_compte_existant(compte, email) compte_existant = Compte.find_by(email: email) if compte_existant.present? - compte.update!(id_inclusion_connect: nil) + compte.update!(id_pro_connect: nil) compte = compte_existant else compte.email = email @@ -99,9 +110,9 @@ def actualise_email_compte_existant(compte, email) end def actualise_autres_champs(compte, user_info) - compte.id_inclusion_connect = user_info['sub'] + compte.id_pro_connect = user_info['sub'] compte.prenom = user_info['given_name'] - compte.nom = user_info['family_name'] + compte.nom = user_info['usual_name'] compte.password = SecureRandom.uuid if compte.encrypted_password.blank? compte.confirmed_at ||= Time.zone.now end diff --git a/app/views/active_admin/devise/sessions/new.html.erb b/app/views/active_admin/devise/sessions/new.html.erb index 420cd4488..a5008927b 100644 --- a/app/views/active_admin/devise/sessions/new.html.erb +++ b/app/views/active_admin/devise/sessions/new.html.erb @@ -16,12 +16,12 @@

<%= t('.pro-connect.transition') %>

- <%= link_to inclusion_connect_auth_path, class:'btn-pro-connect' do %> + <%= link_to pro_connect_auth_path, class:'btn-pro-connect' do %> ProConnect <% end %>

- + <%= t('.pro-connect.libelle-lien-pro-connect') %>

diff --git a/config/initializers/active_admin.rb b/config/initializers/active_admin.rb index 3a0b2c961..44a5ad789 100644 --- a/config/initializers/active_admin.rb +++ b/config/initializers/active_admin.rb @@ -98,7 +98,7 @@ # will call the method to return the path. # # Default: - config.logout_link_path = :inclusion_connect_logout_path + config.logout_link_path = :pro_connect_logout_path # This setting changes the http method used when rendering the # link. For example :get, :delete, :put, etc.. # diff --git a/config/locales/inclusion_connect.yml b/config/locales/inclusion_connect.yml deleted file mode 100644 index 2d845ba39..000000000 --- a/config/locales/inclusion_connect.yml +++ /dev/null @@ -1,3 +0,0 @@ -fr: - inclusion-connect: - authentification-impossible: Nous n'avons pas pu vous authentifier avec Inclusion Connect. Connectez-vous directement sur eva ou réessayez plus tard. diff --git a/config/locales/pro_connect.yml b/config/locales/pro_connect.yml new file mode 100644 index 000000000..873cc7af1 --- /dev/null +++ b/config/locales/pro_connect.yml @@ -0,0 +1,3 @@ +fr: + pro-connect: + authentification-impossible: Nous n'avons pas pu vous authentifier avec ProConnect. Connectez-vous directement sur eva ou réessayez plus tard. diff --git a/config/routes.rb b/config/routes.rb index 4879052cd..606f2bb5e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -13,9 +13,9 @@ end get '/admin', to: redirect('/pro/admin/dashboard') - get "inclusion_connect/logout" => "inclusion_connect#logout" - get "inclusion_connect/auth" => "inclusion_connect#auth" - get "inclusion_connect/callback" => "inclusion_connect#callback" + get "pro_connect/logout" => "pro_connect#logout" + get "pro_connect/auth" => "pro_connect#auth" + get "pro_connect/callback" => "pro_connect#callback" get "demo" => "demo#show" post "demo/connect" => "demo#connect" diff --git a/spec/helpers/inclusion_connect_helper_spec.rb b/spec/helpers/pro_connect_helper_spec.rb similarity index 82% rename from spec/helpers/inclusion_connect_helper_spec.rb rename to spec/helpers/pro_connect_helper_spec.rb index 937405a97..506d9a874 100644 --- a/spec/helpers/inclusion_connect_helper_spec.rb +++ b/spec/helpers/pro_connect_helper_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -describe InclusionConnectHelper do +describe ProConnectHelper do let(:email) { 'toto@eva.beta.gouv.fr' } let(:ancien_email) { 'autre@eva.beta.gouv.fr' } let(:aujourdhui) { Time.zone.local(2023, 1, 10, 12, 0, 0) } @@ -10,14 +10,14 @@ describe '#logout' do it "construit l'url de deconnexion et nettoie la session" do - stub_const('::InclusionConnectHelper::IC_BASE_URL', 'https://IC_HOST') + stub_const('::ProConnectHelper::PC_BASE_URL', 'https://PC_HOST') session = { - ic_state: 'STATE', - ic_logout_token: 'TOKEN' + pc_state: 'STATE', + pc_logout_token: 'TOKEN' } expect(described_class.logout(session, 'https://post_logout_uri')) - .to eq('https://IC_HOST/auth/logout?id_token_hint=TOKEN&post_logout_redirect_uri=https%3A%2F%2Fpost_logout_uri&state=STATE') - expect(session[:ic_logout_token]).to be_nil + .to eq('https://PC_HOST/api/v2/session/end?id_token_hint=TOKEN&post_logout_redirect_uri=https%3A%2F%2Fpost_logout_uri&state=STATE') + expect(session[:pc_logout_token]).to be_nil end end @@ -31,7 +31,7 @@ 'sub' => id_ic, 'email' => email, 'given_name' => 'prénom', - 'family_name' => 'nom' + 'usual_name' => 'nom' }) expect(compte).not_to be_nil expect(compte.email).to eq(email) @@ -39,7 +39,7 @@ expect(compte.nom).to eq('nom') expect(compte.confirmed_at).to eq(aujourdhui) expect(compte.password).not_to be_nil - expect(compte.id_inclusion_connect).to eq(id_ic) + expect(compte.id_pro_connect).to eq(id_ic) end end end @@ -55,7 +55,7 @@ 'sub' => id_ic, 'email' => email, 'given_name' => 'prénom', - 'family_name' => 'nom' + 'usual_name' => 'nom' }) expect(compte).not_to be_nil expect(compte.email).to eq(email) @@ -63,7 +63,7 @@ expect(compte.nom).to eq('nom') expect(compte.confirmed_at).to eq(aujourdhui) expect(compte.password).to be_nil - expect(compte.id_inclusion_connect).to eq(id_ic) + expect(compte.id_pro_connect).to eq(id_ic) end end end @@ -79,7 +79,7 @@ 'sub' => id_ic, 'email' => 'toto@eva.beta.gouv.FR', 'given_name' => 'prénom', - 'family_name' => 'nom' + 'usual_name' => 'nom' }) expect(compte).not_to be_nil expect(compte.email).to eq(email) @@ -87,7 +87,7 @@ expect(compte.nom).to eq('nom') expect(compte.confirmed_at).to eq(aujourdhui) expect(compte.password).to be_nil - expect(compte.id_inclusion_connect).to eq(id_ic) + expect(compte.id_pro_connect).to eq(id_ic) end end end @@ -103,7 +103,7 @@ 'sub' => id_ic, 'email' => email, 'given_name' => 'prénom', - 'family_name' => 'nom' + 'usual_name' => 'nom' }) expect(compte).not_to be_nil expect(compte.prenom).to eq('prénom') @@ -111,7 +111,7 @@ expect(compte.email).to eq(email) expect(compte.confirmed_at).to eq(aujourdhui) expect(compte.password).not_to be_nil - expect(compte.id_inclusion_connect).to eq(id_ic) + expect(compte.id_pro_connect).to eq(id_ic) expect(compte.id).not_to eq(Compte.only_deleted.find_by(email: email).id) end end @@ -119,7 +119,7 @@ context 'le compte existe déjà en base avec id inclusion connect, même email' do before do - create :compte_admin, email: email, confirmed_at: hier, id_inclusion_connect: id_ic + create :compte_admin, email: email, confirmed_at: hier, id_pro_connect: id_ic end it do @@ -128,7 +128,7 @@ 'sub' => id_ic, 'email' => email, 'given_name' => 'prénom', - 'family_name' => 'nom' + 'usual_name' => 'nom' }) expect(compte).not_to be_nil expect(compte.prenom).to eq('prénom') @@ -136,14 +136,14 @@ expect(compte.email).to eq(email) expect(compte.confirmed_at).to eq(hier) expect(compte.password).to be_nil - expect(compte.id_inclusion_connect).to eq(id_ic) + expect(compte.id_pro_connect).to eq(id_ic) end end end context 'le compte existe déjà en base avec id inclusion connect, email différent' do before do - create :compte_admin, email: ancien_email, confirmed_at: hier, id_inclusion_connect: id_ic + create :compte_admin, email: ancien_email, confirmed_at: hier, id_pro_connect: id_ic end it do @@ -152,7 +152,7 @@ 'sub' => id_ic, 'email' => email, 'given_name' => 'prénom', - 'family_name' => 'nom' + 'usual_name' => 'nom' }) expect(compte).not_to be_nil expect(compte.prenom).to eq('prénom') @@ -160,7 +160,7 @@ expect(compte.email).to eq(email) expect(compte.confirmed_at).to eq(aujourdhui) expect(compte.password).to be_nil - expect(compte.id_inclusion_connect).to eq(id_ic) + expect(compte.id_pro_connect).to eq(id_ic) end end end @@ -168,7 +168,7 @@ context "Il existe deux comptes en base dans le cas d'une mise à jourd d'email" do before do create :compte_admin, email: email, confirmed_at: hier - create :compte_admin, email: ancien_email, confirmed_at: hier, id_inclusion_connect: id_ic + create :compte_admin, email: ancien_email, confirmed_at: hier, id_pro_connect: id_ic end it do @@ -177,7 +177,7 @@ 'sub' => id_ic, 'email' => email, 'given_name' => 'prénom', - 'family_name' => 'nom' + 'usual_name' => 'nom' }) expect(compte).not_to be_nil expect(compte.prenom).to eq('prénom') @@ -185,10 +185,10 @@ expect(compte.email).to eq(email) expect(compte.confirmed_at).to eq(hier) expect(compte.password).to be_nil - expect(compte.id_inclusion_connect).to eq(id_ic) + expect(compte.id_pro_connect).to eq(id_ic) ancien_compte = Compte.find_by(email: ancien_email) expect(ancien_compte).not_to be_nil - expect(ancien_compte.id_inclusion_connect).to be_nil + expect(ancien_compte.id_pro_connect).to be_nil end end end @@ -204,7 +204,7 @@ 'sub' => id_ic, 'email' => email, 'given_name' => 'prénom', - 'family_name' => 'nom' + 'usual_name' => 'nom' }) expect(compte.confirmed_at).to eq(hier) end