Skip to content

Commit

Permalink
Merge pull request #128 from etalab/features/subdomain
Browse files Browse the repository at this point in the history
Enable subdomain restrictions on app
  • Loading branch information
skelz0r authored Apr 3, 2024
2 parents e610e27 + de44329 commit 4340115
Show file tree
Hide file tree
Showing 23 changed files with 217 additions and 29 deletions.
1 change: 1 addition & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ Style/FrozenStringLiteralComment:
Style/GlobalVars:
AllowedVariables:
- $elastic
- $previous_app_host

Style/MethodCalledOnDoEndBlock:
Enabled: false
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ Then go to [http://localhost:3000](http://localhost:3000)

For mailer preview: [http://localhost:3000/rails/mailers](http://localhost:3000/rails/mailers)

### Avec un sous-domaine référencé

Il est possible de restreindre l'application à un sous-ensemble de type
d'habilitation à travers un sous nom de domaine. Cela permet de restreindre les
demandeurs à ce sous-ensemble.

Par exemple pour API Entreprise: [http://api-entreprise.localtest.me:3000/](http://api-entreprise.localtest.me:3000/)

Il est possible de bypass le login via MonComptePro de cette manière en local :
[http://api-entreprise.localtest.me:3000/[email protected]](http://api-entreprise.localtest.me:3000/[email protected])

## Tests

With docker:
Expand Down
15 changes: 15 additions & 0 deletions app/controllers/authenticated_user_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ class AuthenticatedUserController < ApplicationController

rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

allow_unauthenticated_access only: :bypass_login

def bypass_login
return unless Rails.env.development?

user = User.find_by(email: params[:email])
sign_in(user)

redirect_to dashboard_path
end

def pundit_user
UserContext.new(current_user, request.host)
end

def user_not_authorized
flash[:error] = {
title: t('application.user_not_authorized.title')
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/authorization_request_forms_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def summary
authorize @authorization_request
@summary_before_submit = @authorization_request.filling?
rescue Pundit::NotAuthorizedError
raise unless AuthorizationRequestPolicy.new(current_user, @authorization_request).show?
raise unless AuthorizationRequestPolicy.new(pundit_user, @authorization_request).show?

redirect_to authorization_request_form_path(form_uid: @authorization_request_form.uid, id: @authorization_request.id)
end
Expand Down
30 changes: 30 additions & 0 deletions app/helpers/subdomains_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module SubdomainsHelper
delegate :host, to: :request

def registered_subdomain
Subdomain.find(app_subdomain)
rescue ActiveRecord::RecordNotFound
nil
end

def registered_subdomain?
registered_subdomain.present?
end

def app_subdomain
case Rails.env
when 'development', 'test'
host.split('.').first
when 'sandbox', 'staging'
host.split('.')[1]
when 'production'
top_level, second_level = host.split('.')[0..1]

if top_level.start_with?('production')
second_level
else
top_level
end
end
end
end
30 changes: 30 additions & 0 deletions app/models/subdomain.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
class Subdomain < StaticApplicationRecord
attr_accessor :id,
:name,
:title,
:tagline,
:authorization_definitions

def self.all
Rails.application.config_for(:subdomains).map do |uid, hash|
build(uid, hash)
end
end

def self.build(uid, hash)
new(
hash.slice(
:name,
:title,
:tagline,
).merge(
id: uid.to_s,
authorization_definitions: AuthorizationDefinition.where(id: hash[:authorization_definition_ids]),
)
)
end

def authorization_request_types
authorization_definitions.map(&:authorization_request_class).flatten.uniq.map(&:to_s)
end
end
14 changes: 14 additions & 0 deletions app/notifiers/api_entreprise_notifier.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class APIEntrepriseNotifier < ApplicationNotifier
AuthorizationRequest.state_machine.states.each do |state|
# rubocop:disable Lint/EmptyBlock
define_method(state.name) do |_params|
end
# rubocop:enable Lint/EmptyBlock
end

def submitted(_params)
Instruction::AuthorizationRequestMailer.with(
authorization_request:
).submitted.deliver_later
end
end
23 changes: 14 additions & 9 deletions app/policies/application_policy.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
class ApplicationPolicy
attr_reader :user, :record
attr_reader :record, :user_context

def initialize(user, record)
@user = user
def initialize(user_context, record)
@user_context = user_context
@record = record
end

delegate :user, to: :user_context

def index?
false
end
Expand Down Expand Up @@ -37,19 +39,22 @@ def destroy?
delegate :current_organization, to: :user, allow_nil: true

class Scope
include SubdomainsHelper

attr_reader :user_context, :scope

delegate :current_organization, to: :user

def initialize(user, scope)
@user = user
def initialize(user_context, scope)
@user_context = user_context
@scope = scope
end

delegate :user, to: :user_context
delegate :host, to: :user_context

def resolve
raise NotImplementedError, "You must define #resolve in #{self.class}"
end

private

attr_reader :user, :scope
end
end
2 changes: 1 addition & 1 deletion app/policies/authorization_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ def show?
private

def authorization_request_policy
@authorization_request_policy ||= AuthorizationRequestPolicy.new(user, record.authorization_request)
@authorization_request_policy ||= AuthorizationRequestPolicy.new(user_context, record.authorization_request)
end
end
8 changes: 7 additions & 1 deletion app/policies/authorization_request_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,13 @@ def another_authorization_request_with_same_type_exists?

class Scope < Scope
def resolve
scope.where(organization: current_organization)
authorization_requests = scope.where(organization: current_organization)

if registered_subdomain?
authorization_requests.where(type: registered_subdomain.authorization_request_types)
else
authorization_requests
end
end
end
end
2 changes: 1 addition & 1 deletion app/policies/instruction/authorization_request_policy.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class Instruction::AuthorizationRequestPolicy < ApplicationPolicy
def show?
Scope.new(user, AuthorizationRequest).resolve.exists?(id: record.id)
Scope.new(user_context, AuthorizationRequest).resolve.exists?(id: record.id)
end

def refuse?
Expand Down
8 changes: 8 additions & 0 deletions app/services/user_context.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class UserContext
attr_reader :user, :host

def initialize(user, host = nil)
@user = user
@host = host
end
end
18 changes: 15 additions & 3 deletions app/views/layouts/_header.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,24 @@
<% end %>

<div class="fr-header__service">
<a href="/" title="Accueil - <%= t('.title') %>">
<% if registered_subdomain? && registered_subdomain.title.present? %>
<% title = registered_subdomain.title %>
<% else %>
<% title = t('.title') %>
<% end %>

<% if registered_subdomain? && registered_subdomain.tagline.present? %>
<% tagline = registered_subdomain.tagline %>
<% else %>
<% tagline = t('.tagline') %>
<% end %>

<a href="/" title="Accueil - <%= title %>">
<p class="fr-header__service-title">
<%= t('.title') %>
<%= title %>
</p>
<p class="fr-header__service-tagline">
<%= t('.tagline') %>
<%= tagline %>
</p>
</a>
</div>
Expand Down
2 changes: 2 additions & 0 deletions config/environments/development.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
require "active_support/core_ext/integer/time"

Rails.application.configure do
config.hosts << /.*\.localtest\.me/

# Configure 'rails notes' to inspect Cucumber files
config.annotations.register_directories('features')
config.annotations.register_extensions('feature') { |tag| /#\s*(#{tag}):?\s*(.*)$/ }
Expand Down
4 changes: 4 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
get 'auth/:provider/callback', to: 'sessions#create'
get 'auth/failure', to: redirect('/')

if Rails.env.development?
get 'local-sign-in', to: 'authenticated_user#bypass_login'
end

get 'compte/deconnexion', to: 'sessions#destroy', as: :signout

get '/tableau-de-bord', to: 'dashboard#index', as: :dashboard
Expand Down
8 changes: 8 additions & 0 deletions config/subdomains.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
shared:
api-entreprise:
name: 'API Entreprise'
title: 'DataPass - API Entreprise'
tagline: 'Habilitations juridiques pour API Entreprise'
authorization_definition_ids:
- 'api_entreprise'
22 changes: 11 additions & 11 deletions features/instruction/modérer_une_habilitation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -4,63 +4,63 @@ Fonctionnalité: Instruction: modération
Un instructeur peut effectuer des actions sur une demande d'habilitation tel que refuser, valider, etc.

Contexte:
Sachant que je suis un instructeur "API Entreprise"
Sachant que je suis un instructeur "API Particulier"
Et que je me connecte

Scénario: Je consulte une demande d'habilitation en brouillon
Quand je me rends sur une demande d'habilitation "API Entreprise" en brouillon
Quand je me rends sur une demande d'habilitation "API Particulier" en brouillon
Alors il n'y a pas de bouton "Valider"
Et il n'y a pas de bouton "Refuser"
Et il n'y a pas de champ éditable

@AvecCourriels
Scénario: Je valide une demande d'habilitation
Quand je me rends sur une demande d'habilitation "API Entreprise" à modérer
Quand je me rends sur une demande d'habilitation "API Particulier" à modérer
Et je clique sur "Valider"
Et je clique sur "Valider la demande d'habilitation"
Alors je suis sur la page "Liste des demandes en cours"
Et je vois 1 demande d'habilitation "API Entreprise" validée
Et je vois 1 demande d'habilitation "API Particulier" validée
Et un email est envoyé contenant "validé"
Et un email est envoyé contenant "vous a désigné(e) comme Responsable de traitement"
Et un email est envoyé contenant "vous a désigné(e) comme Délégué à la protection des données"
Et il y a un message de succès contenant "a été validé"

@AvecCourriels
Scénario: Je refuse une demande d'habilitation avec un message valide
Quand je me rends sur une demande d'habilitation "API Entreprise" à modérer
Quand je me rends sur une demande d'habilitation "API Particulier" à modérer
Et je clique sur "Refuser"
Et que je remplis "Raison du refus" avec "Vous êtes une entreprise privée"
Et que je clique sur "Refuser la demande"
Alors je suis sur la page "Liste des demandes en cours"
Et je vois 1 demande d'habilitation "API Entreprise" refusée
Et je vois 1 demande d'habilitation "API Particulier" refusée
Et un email est envoyé contenant "Vous êtes une entreprise privée"
Et il y a un message de succès contenant "a été refusé"

Scénario: Je refuse une demande d'habilitation avec un message invalide
Quand je me rends sur une demande d'habilitation "API Entreprise" à modérer
Quand je me rends sur une demande d'habilitation "API Particulier" à modérer
Et je clique sur "Refuser"
Et que je clique sur "Refuser la demande"
Alors il y a au moins une erreur sur un champ

@AvecCourriels
Scénario: Je demande des modifications sur une demande d'habilitation avec un message valide
Quand je me rends sur une demande d'habilitation "API Entreprise" à modérer
Quand je me rends sur une demande d'habilitation "API Particulier" à modérer
Et je clique sur "Demander des modifications"
Et que je remplis "Raison de la demande de modification" avec "Précisez votre cas d'usage"
Et que je clique sur "Envoyer la demande de modification"
Alors je suis sur la page "Liste des demandes en cours"
Et je vois 1 demande d'habilitation "API Entreprise" en attente de modification
Et je vois 1 demande d'habilitation "API Particulier" en attente de modification
Et un email est envoyé contenant "Précisez votre cas d'usage"
Et il y a un message de succès contenant "demande de modifications"

Scénario: Je demande des modifications sur une demande d'habilitation avec un message valide
Quand je me rends sur une demande d'habilitation "API Entreprise" à modérer
Quand je me rends sur une demande d'habilitation "API Particulier" à modérer
Et je clique sur "Demander des modifications"
Et que je clique sur "Envoyer la demande de modification"
Alors il y a au moins une erreur sur un champ

Scénario: Je supprime une demande d'habilitation
Quand je me rends sur une demande d'habilitation "API Entreprise" en brouillon
Quand je me rends sur une demande d'habilitation "API Particulier" en brouillon
Et je clique sur "Supprimer"
Et je clique sur "Supprimer la demande"
Alors je suis sur la page "Liste des demandes en cours"
Expand Down
22 changes: 22 additions & 0 deletions features/sous-domaine_tableau_de_bord.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# language: fr

Fonctionnalité: Tableau de bord pour un sous-domaine spécifique
Cette page est la page principale de l'utilisateur, où il peut voir les diverses actions
qu'il doit effectuer. Cette version du tableau de bord est restreinte à un sous-ensemble d'habilitations
régie par le sous-domaine

Contexte:
Sachant que je suis un demandeur
Et que je consulte le site ayant le sous-domaine "api-entreprise"
Et que je me connecte

Scénario: Je vois sur l'écran principal l'ensemble de mes habilitations uniquement lié au sous-domaine, quelque soit leur état
Quand j'ai 1 demande d'habilitation "API Entreprise" en brouillon
Et j'ai 1 demande d'habilitation "API Entreprise" en attente
Et j'ai 1 demande d'habilitation "API Entreprise" refusée
Et j'ai 1 demande d'habilitation "API Entreprise" validée
Et j'ai 1 demande d'habilitation "API Particulier" validée
Et que mon organisation a 1 demande d'habilitation "API Entreprise"
Et que je vais sur la page du tableau de bord
Alors je vois 4 demandes d'habilitation
Et la page contient "Vous êtes le demandeur"
5 changes: 5 additions & 0 deletions features/step_definitions/login_steps.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ def mock_mon_compte_pro(user)
mock_mon_compte_pro(user)
end

Sachantque('je consulte le site ayant le sous-domaine {string}') do |subdomain|
$previous_app_host = Capybara.app_host.dup
Capybara.app_host = "http://#{subdomain}.localtest.me"
end

Sachantque('je suis un instructeur {string}') do |kind|
user = create_instructor(kind)

Expand Down
5 changes: 5 additions & 0 deletions features/support/javascript.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
load Rails.root.join('spec/support/configure_javascript_driver.rb')

Before do |scenario|
if $previous_app_host
Capybara.app_host = $previous_app_host
$previous_app_host = nil
end

@javascript = scenario.source_tag_names.include?('@javascript')
end

Expand Down
Loading

0 comments on commit 4340115

Please sign in to comment.