Merge pull request #10154 from demarches-simplifiees/multiple_agent_connect
Tech: permet de faire marcher agent connect simultanément sur le domaine gouv et ds
This commit is contained in:
commit
7178660acd
13 changed files with 97 additions and 56 deletions
|
@ -20,22 +20,18 @@ class AgentConnect::AgentController < ApplicationController
|
|||
|
||||
def callback
|
||||
user_info, id_token = AgentConnectService.user_info(params[:code], cookies.encrypted[NONCE_COOKIE_NAME])
|
||||
cookies.encrypted[NONCE_COOKIE_NAME] = nil
|
||||
cookies.delete NONCE_COOKIE_NAME
|
||||
|
||||
instructeur = Instructeur.find_by(agent_connect_id: user_info['sub'])
|
||||
|
||||
if instructeur.nil?
|
||||
instructeur = Instructeur.find_by(users: { email: santized_email(user_info) })
|
||||
end
|
||||
instructeur = Instructeur.find_by(users: { email: santized_email(user_info) })
|
||||
|
||||
if instructeur.nil?
|
||||
user = User.create_or_promote_to_instructeur(santized_email(user_info), Devise.friendly_token[0, 20])
|
||||
instructeur = user.instructeur
|
||||
end
|
||||
|
||||
instructeur.update(agent_connect_id: user_info['sub'], agent_connect_id_token: id_token)
|
||||
instructeur.update(agent_connect_id_token: id_token)
|
||||
|
||||
aci = AgentConnectInformation.find_or_initialize_by(instructeur:)
|
||||
aci = AgentConnectInformation.find_or_initialize_by(instructeur:, sub: user_info['sub'])
|
||||
aci.update(user_info.slice('given_name', 'usual_name', 'email', 'sub', 'siret', 'organizational_unit', 'belonging_population', 'phone'))
|
||||
|
||||
sign_in(:user, instructeur.user)
|
||||
|
@ -69,7 +65,7 @@ class AgentConnect::AgentController < ApplicationController
|
|||
flash.alert = t('errors.messages.france_connect.connexion')
|
||||
redirect_to(new_user_session_path)
|
||||
else
|
||||
cookies.encrypted[STATE_COOKIE_NAME] = nil
|
||||
cookies.delete STATE_COOKIE_NAME
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -56,7 +56,7 @@ class RecoveriesController < ApplicationController
|
|||
private
|
||||
|
||||
def nature_params = params[:nature]
|
||||
def siret = current_instructeur.agent_connect_information.siret
|
||||
def siret = current_instructeur.last_agent_connect_information.siret
|
||||
def previous_email = params[:previous_email]
|
||||
def procedure_ids = params[:procedure_ids].map(&:to_i)
|
||||
|
||||
|
@ -70,7 +70,7 @@ class RecoveriesController < ApplicationController
|
|||
end
|
||||
|
||||
def ensure_agent_connect_is_used
|
||||
if current_instructeur&.agent_connect_information.nil?
|
||||
if current_instructeur&.last_agent_connect_information.nil?
|
||||
redirect_to support_recovery_path(error: :must_use_agent_connect)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -110,6 +110,6 @@ class Users::SessionsController < Devise::SessionsController
|
|||
|
||||
def build_agent_connect_logout_url(id_token)
|
||||
h = { id_token_hint: id_token, post_logout_redirect_uri: logout_url }
|
||||
"#{ENV['AGENT_CONNECT_BASE_URL']}/api/v2/session/end?#{h.to_query}"
|
||||
"#{AGENT_CONNECT[:end_session_endpoint]}?#{h.to_query}"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
class AgentConnectClient < OpenIDConnect::Client
|
||||
def initialize(code = nil)
|
||||
super(AGENT_CONNECT)
|
||||
|
||||
if code.present?
|
||||
self.authorization_code = code
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,8 +1,10 @@
|
|||
class Instructeur < ApplicationRecord
|
||||
self.ignored_columns += [:agent_connect_id]
|
||||
|
||||
include UserFindByConcern
|
||||
has_and_belongs_to_many :administrateurs
|
||||
|
||||
has_one :agent_connect_information, dependent: :destroy
|
||||
has_many :agent_connect_information, dependent: :destroy
|
||||
|
||||
has_many :assign_to, dependent: :destroy
|
||||
has_many :groupe_instructeurs, -> { order(:label) }, through: :assign_to
|
||||
|
@ -296,6 +298,10 @@ class Instructeur < ApplicationRecord
|
|||
.update_all(claimant_id: id)
|
||||
end
|
||||
|
||||
def last_agent_connect_information
|
||||
agent_connect_information.order(updated_at: :desc).first
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def annotations_hash(demande, annotations_privees, avis, messagerie)
|
||||
|
|
|
@ -2,11 +2,11 @@ class AgentConnectService
|
|||
include OpenIDConnect
|
||||
|
||||
def self.enabled?
|
||||
ENV.fetch("AGENT_CONNECT_ENABLED", "enabled") == "enabled"
|
||||
ENV['AGENT_CONNECT_BASE_URL'].present?
|
||||
end
|
||||
|
||||
def self.authorization_uri
|
||||
client = AgentConnectClient.new
|
||||
client = OpenIDConnect::Client.new(conf)
|
||||
|
||||
state = SecureRandom.hex(16)
|
||||
nonce = SecureRandom.hex(16)
|
||||
|
@ -22,25 +22,25 @@ class AgentConnectService
|
|||
end
|
||||
|
||||
def self.user_info(code, nonce)
|
||||
client = AgentConnectClient.new(code)
|
||||
client = OpenIDConnect::Client.new(conf)
|
||||
client.authorization_code = code
|
||||
|
||||
access_token = client.access_token!(client_auth_method: :secret)
|
||||
|
||||
discover = find_discover
|
||||
id_token = ResponseObject::IdToken.decode(access_token.id_token, discover.jwks)
|
||||
|
||||
id_token.verify!(
|
||||
client_id: Rails.application.secrets.agent_connect[:identifier],
|
||||
issuer: discover.issuer,
|
||||
nonce: nonce
|
||||
)
|
||||
id_token = ResponseObject::IdToken.decode(access_token.id_token, conf[:jwks])
|
||||
id_token.verify!(conf.merge(nonce: nonce))
|
||||
|
||||
[access_token.userinfo!.raw_attributes, access_token.id_token]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.find_discover
|
||||
Discovery::Provider::Config.discover!("#{ENV.fetch('AGENT_CONNECT_BASE_URL')}/api/v2")
|
||||
# TODO: remove this block when migration to new domain is done
|
||||
def self.conf
|
||||
if Current.host.end_with?('.gouv.fr')
|
||||
AGENT_CONNECT_GOUV
|
||||
else
|
||||
AGENT_CONNECT
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
%h2 Identification du propriétaire des dossiers
|
||||
|
||||
%p Votre organisation est « #{@structure_name} » identifiée par le SIRET #{current_instructeur.agent_connect_information.siret}
|
||||
%p Votre organisation est « #{@structure_name} » identifiée par le SIRET #{current_instructeur.last_agent_connect_information.siret}
|
||||
= form_with do |f|
|
||||
.fr-input-group
|
||||
%label.fr-label{ for: "email" }
|
||||
|
|
|
@ -52,12 +52,6 @@ FC_PARTICULIER_ID=""
|
|||
FC_PARTICULIER_SECRET=""
|
||||
FC_PARTICULIER_BASE_URL=""
|
||||
|
||||
# External service: authentication through Agent Connect
|
||||
AGENT_CONNECT_ID=""
|
||||
AGENT_CONNECT_SECRET=""
|
||||
AGENT_CONNECT_BASE_URL=""
|
||||
AGENT_CONNECT_REDIRECT=""
|
||||
|
||||
# External service: integration with HelpScout (optional)
|
||||
HELPSCOUT_MAILBOX_ID=""
|
||||
HELPSCOUT_CLIENT_ID=""
|
||||
|
|
|
@ -19,8 +19,16 @@ DS_ENV="staging"
|
|||
# France Connect usage
|
||||
# FRANCE_CONNECT_ENABLED="disabled" # "enabled" by default
|
||||
|
||||
# Agent Connect usage
|
||||
# AGENT_CONNECT_ENABLED="disabled" # "enabled" by default
|
||||
# External service: authentication through Agent Connect
|
||||
# AGENT_CONNECT_ID=""
|
||||
# AGENT_CONNECT_SECRET=""
|
||||
# AGENT_CONNECT_BASE_URL=""
|
||||
# AGENT_CONNECT_REDIRECT=""
|
||||
|
||||
# useful when migrating to gouv domain
|
||||
# AGENT_CONNECT_GOUV_ID=""
|
||||
# AGENT_CONNECT_GOUV_SECRET=""
|
||||
# AGENT_CONNECT_GOUV_REDIRECT=""
|
||||
|
||||
# Certigna usage
|
||||
# CERTIGNA_ENABLED="disabled" # "enabled" by default
|
||||
|
|
|
@ -1 +1,27 @@
|
|||
AGENT_CONNECT = Rails.application.secrets.agent_connect
|
||||
if ENV['AGENT_CONNECT_BASE_URL'].present?
|
||||
discover = OpenIDConnect::Discovery::Provider::Config.discover!("#{ENV.fetch('AGENT_CONNECT_BASE_URL')}/api/v2")
|
||||
|
||||
AGENT_CONNECT = {
|
||||
issuer: discover.issuer,
|
||||
jwks: discover.jwks,
|
||||
authorization_endpoint: discover.authorization_endpoint,
|
||||
token_endpoint: discover.token_endpoint,
|
||||
userinfo_endpoint: discover.userinfo_endpoint,
|
||||
end_session_endpoint: discover.end_session_endpoint,
|
||||
client_id: ENV.fetch('AGENT_CONNECT_ID'),
|
||||
identifier: ENV.fetch('AGENT_CONNECT_ID'),
|
||||
secret: ENV.fetch('AGENT_CONNECT_SECRET'),
|
||||
redirect_uri: ENV.fetch('AGENT_CONNECT_REDIRECT')
|
||||
}
|
||||
|
||||
if ENV['AGENT_CONNECT_GOUV_ID'].present?
|
||||
gouv_conf = AGENT_CONNECT.dup
|
||||
|
||||
gouv_conf[:client_id] = ENV.fetch('AGENT_CONNECT_GOUV_ID')
|
||||
gouv_conf[:identifier] = ENV.fetch('AGENT_CONNECT_GOUV_ID')
|
||||
gouv_conf[:secret] = ENV.fetch('AGENT_CONNECT_GOUV_SECRET')
|
||||
gouv_conf[:redirect_uri] = ENV.fetch('AGENT_CONNECT_GOUV_REDIRECT')
|
||||
|
||||
AGENT_CONNECT_GOUV = gouv_conf
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,14 +27,6 @@ defaults: &defaults
|
|||
token_endpoint: <%= ENV['FC_PARTICULIER_BASE_URL'] %>/api/v1/token
|
||||
userinfo_endpoint: <%= ENV['FC_PARTICULIER_BASE_URL'] %>/api/v1/userinfo
|
||||
logout_endpoint: <%= ENV['FC_PARTICULIER_BASE_URL'] %>/api/v1/logout
|
||||
agent_connect:
|
||||
identifier: <%= ENV['AGENT_CONNECT_ID'] %>
|
||||
secret: <%= ENV['AGENT_CONNECT_SECRET'] %>
|
||||
redirect_uri: <%= ENV['AGENT_CONNECT_REDIRECT'] %>
|
||||
authorization_endpoint: <%= ENV['AGENT_CONNECT_BASE_URL'] %>/api/v2/authorize
|
||||
token_endpoint: <%= ENV['AGENT_CONNECT_BASE_URL'] %>/api/v2/token
|
||||
userinfo_endpoint: <%= ENV['AGENT_CONNECT_BASE_URL'] %>/api/v2/userinfo
|
||||
logout_endpoint: <%= ENV['AGENT_CONNECT_BASE_URL'] %>/api/v2/session/end
|
||||
dolist:
|
||||
username: <%= ENV['DOLIST_USERNAME'] %>
|
||||
password: <%= ENV['DOLIST_PASSWORD'] %>
|
||||
|
|
|
@ -50,7 +50,6 @@ describe AgentConnect::AgentController, type: :controller do
|
|||
|
||||
expect(last_user.email).to eq(email)
|
||||
expect(last_user.confirmed_at).to be_present
|
||||
expect(last_user.instructeur.agent_connect_id).to eq('sub')
|
||||
expect(last_user.instructeur.agent_connect_id_token).to eq('id_token')
|
||||
expect(response).to redirect_to(instructeur_procedures_path)
|
||||
expect(state_cookie).to be_nil
|
||||
|
@ -70,7 +69,6 @@ describe AgentConnect::AgentController, type: :controller do
|
|||
|
||||
instructeur.reload
|
||||
|
||||
expect(instructeur.agent_connect_id).to eq('sub')
|
||||
expect(instructeur.agent_connect_id_token).to eq('id_token')
|
||||
expect(response).to redirect_to(instructeur_procedures_path)
|
||||
end
|
||||
|
@ -88,13 +86,42 @@ describe AgentConnect::AgentController, type: :controller do
|
|||
|
||||
instructeur = user.reload.instructeur
|
||||
|
||||
expect(instructeur.agent_connect_id).to eq('sub')
|
||||
expect(instructeur.agent_connect_id_token).to eq('id_token')
|
||||
expect(response).to redirect_to(instructeur_procedures_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the instructeur connects two times with the same domain' do
|
||||
before do
|
||||
expect(AgentConnectService).to receive(:user_info).with(code, nonce).and_return([user_info, id_token]).twice
|
||||
expect(controller).to receive(:sign_in).twice
|
||||
end
|
||||
|
||||
it 'creates another agent_connect_information' do
|
||||
get :callback, params: { code: code, state: state }
|
||||
get :callback, params: { code: code, state: state }
|
||||
|
||||
expect(Instructeur.last.agent_connect_information.count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the instructeur connects two times with different domains' do
|
||||
before do
|
||||
expect(controller).to receive(:sign_in).twice
|
||||
end
|
||||
|
||||
it 'creates another agent_connect_information' do
|
||||
expect(AgentConnectService).to receive(:user_info).with(code, nonce).and_return([user_info, id_token])
|
||||
get :callback, params: { code: code, state: state }
|
||||
|
||||
expect(AgentConnectService).to receive(:user_info).with(code, nonce).and_return([user_info.merge('sub' => 'sub2'), id_token])
|
||||
get :callback, params: { code: code, state: state }
|
||||
|
||||
expect(Instructeur.last.agent_connect_information.pluck(:sub)).to match_array(['sub', 'sub2'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'but user_info raises and error' do
|
||||
before do
|
||||
expect(AgentConnectService).to receive(:user_info).and_raise(Rack::OAuth2::Client::Error.new(500, error: 'Unknown'))
|
||||
|
|
|
@ -85,6 +85,7 @@ describe Users::SessionsController, type: :controller do
|
|||
let(:agent_connect_id_token) { nil }
|
||||
|
||||
before do
|
||||
stub_const("AGENT_CONNECT", { end_session_endpoint: 'http://agent-connect/logout' })
|
||||
sign_in user
|
||||
delete :destroy
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue