Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Permettre aux agents de se créer un compte sans organisation via Proconnect #5102

Merged
merged 21 commits into from
Mar 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions app/controllers/admin/organisations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ class Admin::OrganisationsController < AgentAuthController
respond_to :html, :json

before_action :set_organisation, except: :index
before_action :follow_unique, only: :index

def index
@organisations_by_territory = policy_scope(current_agent.organisations, policy_scope_class: Agent::OrganisationPolicy::Scope)
Expand Down Expand Up @@ -73,11 +72,4 @@ def organisation_params
def new_organisation_params
params.require(:organisation).permit(:name, :territory_id)
end

def follow_unique
accessible_organisations = policy_scope(Organisation, policy_scope_class: Agent::OrganisationPolicy::Scope)
return if params[:follow_unique].blank? || accessible_organisations.count != 1

redirect_to admin_organisation_agent_agenda_path(accessible_organisations.first, current_agent)
end
end
6 changes: 5 additions & 1 deletion app/controllers/agent_connect_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def auth
redirect_to auth_client.redirect_url(agent_connect_callback_url), allow_other_host: true
end

def callback
def callback # rubocop:disable Metrics/PerceivedComplexity
callback_client = AgentConnectOpenIdClient::Callback.new(
session_state: session.delete(:agent_connect_state),
params_state: params[:state],
Expand All @@ -33,6 +33,10 @@ def callback
# voir https://github.com/numerique-gouv/agentconnect-documentation/blob/main/doc_fs/donnees_fournies.md#le-champ-sub
agent = Agent.active.find_by(email: callback_client.user_email)

if current_domain.allow_agent_creation_with_agent_connect
agent ||= Agent.new(email: callback_client.user_email, password: SecureRandom.base64(32))
end

if agent
agent.update!(
connected_with_agent_connect: true,
Expand Down
23 changes: 23 additions & 0 deletions app/controllers/agents/pages_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class Agents::PagesController < AgentAuthController
layout "application"

CONTACT_TEAM_URL = "https://cal.com/forms/937585aa-48a4-4efd-a642-961fad79c9c5".freeze

def home
skip_authorization

accessible_organisations = policy_scope(Organisation, policy_scope_class: Agent::OrganisationPolicy::Scope)

if accessible_organisations.count == 1
redirect_to admin_organisation_agent_agenda_path(accessible_organisations.first, current_agent)
elsif accessible_organisations.count > 1
redirect_to admin_organisations_path
end
end

private

def pundit_user
AgentContext.new(current_agent)
end
end
11 changes: 10 additions & 1 deletion app/controllers/super_admins/comptes_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
module SuperAdmins
class ComptesController < SuperAdmins::ApplicationController
def new
@agent = Agent.find_by(id: params[:agent_id])
if @agent
authorize_resource(@agent)
end

super
end

def create
compte_params[:agent][:invited_by] = current_super_admin
compte = Compte.new(compte_params, current_domain)
Expand All @@ -24,7 +33,7 @@ def compte_params
territory: %i[name departement_number],
organisation: %i[name ants_connectable],
lieu: %i[address latitude longitude],
agent: %i[first_name last_name email service_ids]
agent: %i[id first_name last_name email service_ids]
)
end
end
Expand Down
22 changes: 18 additions & 4 deletions app/form_models/compte.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,7 @@ def save!
organisation.save!
lieu.save!

self.agent = Agent.invite!(@attributes[:agent].merge(
password: SecureRandom.base64(32),
roles_attributes: [{ organisation: organisation, access_level: AgentRole::ACCESS_LEVEL_ADMIN }]
))
self.agent = find_or_invite_agent(organisation)

agent.services.each do |service|
TerritoryService.create!(service: service, territory: territory)
Expand Down Expand Up @@ -65,6 +62,23 @@ def to_s

private

def find_or_invite_agent(organisation)
if @attributes.dig(:agent, :id)
Agent.find(@attributes.dig(:agent, :id)).tap do |agent|
agent.update(
@attributes[:agent].merge(
roles_attributes: [{ organisation: organisation, access_level: AgentRole::ACCESS_LEVEL_ADMIN }]
)
)
end
else
Agent.invite!(@attributes[:agent].merge(
roles_attributes: [{ organisation: organisation, access_level: AgentRole::ACCESS_LEVEL_ADMIN }],
password: SecureRandom.base64(32)
))
end
end

def create_mairie_motifs!
service = Service.find_by(name: Service::MAIRIE)

Expand Down
4 changes: 4 additions & 0 deletions app/javascript/stylesheets/administrate/base/_forms.scss
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,7 @@ select {
outline-offset: $focus-outline-offset;
}
}

.form-group {
margin: 8px 0;
}
2 changes: 1 addition & 1 deletion app/models/agent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def timeout_in = 14.days # Used by Devise's :timeoutable
# * it validates :email (the invite_key) specifically with Devise.email_regexp.
validates :first_name, presence: true, unless: -> { allow_blank_name || is_an_intervenant? }
validates :last_name, presence: true, unless: -> { allow_blank_name }
validates :agent_services, presence: true
validates :agent_services, presence: true, unless: -> { roles.none? }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je prédit que l'omelette ne se fera pas sans casser des œufs, mais moi aussi j'ai faim ! 😉


# Hooks
before_destroy :prevent_destroy_if_rdvs
Expand Down
4 changes: 4 additions & 0 deletions app/models/domain.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
:support_email,
:secretariat_email,
:verticale,
:allow_agent_creation_with_agent_connect,
keyword_init: true
)

Expand All @@ -36,6 +37,7 @@ class Domain
france_connect_enabled: true,
support_email: "[email protected]",
verticale: :rdv_solidarites,
allow_agent_creation_with_agent_connect: false,
secretariat_email: "[email protected]"
# secretariat_email est utilisé comme adresse de "Reply-To" pour les e-mails
# qui contiennent des ICS. Lorsque l'événement ICS est acceptée par le
Expand All @@ -58,6 +60,7 @@ class Domain
france_connect_enabled: false,
support_email: "[email protected]",
verticale: :rdv_aide_numerique,
allow_agent_creation_with_agent_connect: false,
secretariat_email: "[email protected]"
),

Expand All @@ -76,6 +79,7 @@ class Domain
france_connect_enabled: true,
support_email: "[email protected]",
verticale: :rdv_mairie,
allow_agent_creation_with_agent_connect: true,
secretariat_email: "[email protected]"
),
].freeze
Expand Down
3 changes: 3 additions & 0 deletions app/services/admin_creates_agent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ def call
@agent = find_agent

if @agent
if @agent.services.none?
@agent.update(service_ids: @agent_params[:service_ids])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Et si dans app/controllers/admin/agents_controller.rb:24 on utilise Agent.new(create_agent_params.merge(...)), ça nous permet d'ajouter des conditions sur les services dans la policy. Je ne sais pas si on veut aller jusque là, c'est juste pour évoquer le sujet.

Disons que là je vois qu'on utilise des service_ids qui ne sont jamais observés dans la policy, et donc ça fait sonner l'alerte, mais bon, c'est des service_ids, pas de user_ids. Mais quand même, ça peut être une faille d'escalade de droits dans le futur.

end
add_agent_to_organisations
@warning_message = self.class.check_agent_service(@agent, @agent_params[:service_ids])
elsif @access_level == "intervenant"
Expand Down
17 changes: 17 additions & 0 deletions app/views/agents/pages/home.html.slim
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.fr-container.fr-pb-8w
h1.fr-h3.fr-pb-2w Bienvenue !
p.fr-py-1w Pour commencer, aidez-nous à en savoir plus :

p.fr-pt-1w.fr-pb-2w
| Votre structure <b>utilise déjà #{current_domain.name}</b> et vous souhaitez disposer d’un accès ?
br
| Vos collègues peuvent vous inviter depuis le menu <b>“Paramètres > Agents > Ajouter un agent”</b>.

p.fr-py-1w
| Votre structure <b>n’utilise pas #{current_domain.name}</b> et vous souhaitez créer un compte ?
br
| Nous vous invitons à contacter notre équipe. Nous organiserons un temps d’échanges pour vous présenter la solution et créer le compte de votre structure.

ul.fr-btns-group.fr-btns-group--center.fr-btns-group--inline-md.fr-mb-8w
li
= link_to "Contacter notre équipe", "https://cal.com/team/rdv-service-public/temps-d-echanges", class: "fr-btn"
2 changes: 1 addition & 1 deletion app/views/dsfr/rdv_mairie/homepage.html.slim
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
- contact_team_url = "https://cal.com/forms/937585aa-48a4-4efd-a642-961fad79c9c5"
- contact_team_url = Agents::PagesController::CONTACT_TEAM_URL

.fr-py-8w.rdv-background-color-alt-blue-ecume
.fr-container
Expand Down
5 changes: 4 additions & 1 deletion app/views/super_admins/agents/show.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ header.main-content__header role="banner"
=> link_to "Inviter", invite_super_admins_agent_path(page.resource), method: :post, class: "button", data: { disable_with: "Veuillez patienter…"} if accessible_action?(page.resource, :invite)
- if sign_in_as_allowed?
=> link_to "Se logger en tant que", sign_in_as_super_admins_agent_path(page.resource), class: "button" if accessible_action?(page.resource, :sign_in_as)
=> link_to(t("administrate.actions.edit_resource", name: page.page_title), [:edit, namespace, page.resource], class: "button") if accessible_action?(page.resource, :edit)
=> link_to("Modifier", [:edit, namespace, page.resource], class: "button") if accessible_action?(page.resource, :edit)
=> link_to("Migrer", new_super_admins_agent_migration_path(agent_id: page.resource.id), class: "button")
- if page.resource.roles.none?
= link_to("Ouvrir un compte", new_super_admins_compte_path(agent_id: page.resource.id), class: "button")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trop malin ! 🤩


section.main-content__body
dl
- page.attributes.each do |attribute|
Expand Down
20 changes: 12 additions & 8 deletions app/views/super_admins/comptes/new.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ section.main-content__body
p
| Ce formulaire vous permet de créer un territoire, une organisation, et un lieu pour un agent qui en sera admin (généralement la personne référente du projet).
p
| Un motif d'exemple "Mon premier motif" sera créé, sauf s'il s'agit d'une ouverture de compte pour une mairie.
| Des motifs "Suivi de dossier" seront créés par défaut
= simple_form_for([namespace, page.resource], html: { class: "form" }) do |f|
= f.simple_fields_for :territory do |ff|
= ff.input :name, label: "Nom du territoire"

= f.simple_fields_for :organisation do |ff|
= ff.input :name, label: "Nom de la première organisation et du premier lieu"
div[style="margin: 8px 0"]
= ff.input :name, label: "Nom de la première organisation et du premier lieu"
div[style="margin: 16px 0 8px 0"]
= ff.input :ants_connectable, as: :boolean, label: "Autoriser le branchement au moteur de recherche de l'ANTS", hint: "En cochant cette case, vous permettrez à cette organisation (probablement une mairie) d'apparaitre sur https://rendezvouspasseport.ants.gouv.fr/"

Expand All @@ -32,11 +33,14 @@ section.main-content__body

h2[style="margin: 32px 0 16px 0"] Admin de territoire
= f.simple_fields_for :agent do |ff|
= ff.input :first_name, label: "Prénom"
= ff.input :last_name, label: "Nom"
= ff.input :email, label: "Adresse mail", hint: "Une invitation sera envoyée automatiquement"
- # rubocop:disable Rails/OutputSafety
= ff.input :service_ids, label: "Service", collection: Service.all, hint: "Si nécessaire, vous pouvez #{link_to('créer un nouveau service', new_super_admins_service_path, target: :blank)}".html_safe
- # rubocop:enable Rails/OutputSafety
- if @agent
p = @agent.full_name
p = @agent.email
= ff.hidden_field :id, value: @agent.id
- else
= ff.input :first_name, label: "Prénom"
= ff.input :last_name, label: "Nom"
= ff.input :email, label: "Adresse mail", hint: "Une invitation sera envoyée automatiquement"
= ff.input :service_ids, label: "Service", collection: Service.all, hint: sanitize("Si nécessaire, vous pouvez #{link_to('créer un nouveau service', new_super_admins_service_path, target: :blank)}")

= f.submit
2 changes: 1 addition & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@
end
end
authenticated :agent do
root to: "admin/organisations#index", as: :authenticated_agent_root, defaults: { follow_unique: "1" }
root to: "agents/pages#home", as: :authenticated_agent_root
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beaucoup plus compréhensible et simple !

end

scope path: "prescripteur", as: "prescripteur", controller: "prescripteur_rdv_wizard" do
Expand Down
15 changes: 15 additions & 0 deletions db/seeds/ccas.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,18 @@

user.skip_confirmation!
user.save!

# Un agent pour tester l'absence d'orga et de services
agent = Agent.new(
email: "[email protected]",
uid: "[email protected]",
first_name: "Bob",
last_name: "Sans Organisation",
password: "Rdvservicepublictest1!",
services: [],
invitation_accepted_at: 1.day.ago,
roles_attributes: [],
agent_territorial_access_rights_attributes: []
)
agent.skip_confirmation!
agent.save!
9 changes: 9 additions & 0 deletions spec/factories/agent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,17 @@

transient do
service { build(:service) }
no_services { false }

trait :no_services do
no_services { true }
end
end
after(:build) do |agent, evaluator|
next if evaluator.no_services
next if agent.agent_services.any?
next if agent.services.any?

if agent.agent_services.empty? && agent.services.empty?
agent.services = if evaluator.service
[evaluator.service]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
fill_in "Mot de passe", with: "correct H0rse battery! staple"
expect { click_on "Enregistrer" }.to change { agent.reload.encrypted_password }
expect(page).to have_content("Votre mot de passe a été édité avec succès")
expect(page).to have_link("Vos organisations")
end

it "works when using the user's password reset form" do
Expand All @@ -39,6 +38,5 @@
fill_in "Mot de passe", with: "correct H0rse battery! staple"
expect { click_on "Enregistrer" }.to change { agent.reload.encrypted_password }
expect(page).to have_content("Votre mot de passe a été édité avec succès")
expect(page).to have_link("Vos organisations")
end
end
27 changes: 27 additions & 0 deletions spec/services/admin_creates_agent_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
RSpec.describe AdminCreatesAgent do
context "when inviting an agent that doesn't have any services" do
let(:agent) do
create(:agent, :no_services, organisations: [])
end
let(:service1) { create(:service) }
let(:service2) { create(:service) }
let(:organisation) do
create(:organisation)
end
let(:admin) do
create(:agent, admin_role_in_organisations: [organisation])
end

it "adds the services to the agent" do
described_class.new(
agent_params: { email: agent.email, service_ids: [service1.id, service2.id] },
current_agent: admin,
organisations: [organisation],
access_level: :basic
).call

expect(agent.reload.services).to contain_exactly(service1, service2)
expect(agent.organisations).to eq [organisation]
end
end
end
Loading