Merge pull request #8623 from tchak/feat-api-token-procedure
feat(api_token): enable fine grained access rights on tokens
This commit is contained in:
commit
9ead0daf32
23 changed files with 346 additions and 42 deletions
|
@ -161,6 +161,13 @@
|
|||
margin-left: auto;
|
||||
}
|
||||
|
||||
.truncate-80 {
|
||||
width: 80%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
// generate spacer utility like bootstrap my-2 -> margin-left/right: 2 * $default-spacer
|
||||
// using $direction.key as css modifier, $direction.values to set css properties
|
||||
// scale it using $steps
|
||||
|
|
|
@ -9,7 +9,16 @@
|
|||
height: 24px;
|
||||
margin: 0;
|
||||
margin-right: 15px;
|
||||
margin-bottom: 40px;
|
||||
|
||||
margin-bottom: $default-fields-spacer;
|
||||
|
||||
&.small-margin {
|
||||
margin-bottom: $default-spacer;
|
||||
}
|
||||
|
||||
&.no-margin {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Hide default HTML checkbox
|
||||
|
|
|
@ -6,7 +6,10 @@
|
|||
= render Dsfr::ListComponent.new do |list|
|
||||
- api_and_packed_tokens.each do |(api_token, packed_token)|
|
||||
- list.with_item do
|
||||
= render Profile::APITokenComponent.new(api_token:, packed_token:)
|
||||
.fr-card.fr-card--horizontal
|
||||
.fr-card__body.width-100
|
||||
.fr-card__content
|
||||
= render Profile::APITokenComponent.new(api_token:, packed_token:)
|
||||
|
||||
%br
|
||||
= button_to "Créer et afficher un nouveau jeton", api_tokens_path, method: :post, class: "fr-btn fr-btn--secondary"
|
||||
|
|
|
@ -3,4 +3,14 @@ class Profile::APITokenComponent < ApplicationComponent
|
|||
@api_token = api_token
|
||||
@packed_token = packed_token
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def procedures_to_allow_options
|
||||
@api_token.procedures_to_allow.map { ["#{_1.id} – #{_1.libelle}", _1.id] }
|
||||
end
|
||||
|
||||
def procedures_to_allow_select_options
|
||||
{ selected: @api_token.procedures_to_allow.first&.id }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
fr:
|
||||
allowed_full_access_html: Ce jeton a accès à <strong>toutes</strong> les démarches attachées à votre compte administrateur
|
||||
allowed_procedures_html:
|
||||
zero: Ce jeton n’a accès à <strong>aucune</strong> démarche
|
||||
one: Ce jeton a accès a une démarche sélectionnée
|
||||
other: Ce jeton a accès a %{count} démarches sélectionnées
|
|
@ -1,15 +1,53 @@
|
|||
%p
|
||||
%h3.fr-card__title
|
||||
%b= "#{@api_token.name} "
|
||||
%span.fr-text--sm= @api_token.prefix
|
||||
|
||||
- if @packed_token.present?
|
||||
.fr-text--sm{ style: "width: 80%; word-break: break-all;" }
|
||||
- button = render Dsfr::CopyButtonComponent.new(text: @packed_token, title: "Copier le jeton dans le presse-papier", success: "Le jeton a été copié dans le presse-papier")
|
||||
= "#{@packed_token} #{button}"
|
||||
.fr-card__desc
|
||||
- if @packed_token.present?
|
||||
.fr-text--sm{ style: "width: 80%; word-break: break-all;" }
|
||||
- button = render Dsfr::CopyButtonComponent.new(text: @packed_token, title: "Copier le jeton dans le presse-papier", success: "Le jeton a été copié dans le presse-papier")
|
||||
= "#{@packed_token} #{button}"
|
||||
|
||||
%p Pour des raisons de sécurité, il ne sera plus ré-affiché, notez-le bien.
|
||||
%p Pour des raisons de sécurité, il ne sera plus ré-affiché, notez-le bien.
|
||||
|
||||
- else
|
||||
%p Pour des raisons de sécurité, nous ne pouvons vous l’afficher que lors de sa création.
|
||||
- else
|
||||
%p Pour des raisons de sécurité, nous ne pouvons vous l’afficher que lors de sa création.
|
||||
|
||||
= button_to "Révoquer le jeton", api_token_path(@api_token), method: :delete, class: "fr-btn fr-btn--secondary", data: { turbo_confirm: "Confirmez-vous la révocation de ce jeton ? Les applications qui l’utilisent actuellement seront bloquées." }
|
||||
- if @api_token.full_access?
|
||||
%p.fr-text--lg
|
||||
= t('.allowed_full_access_html')
|
||||
- else
|
||||
%p.fr-text--lg
|
||||
= t('.allowed_procedures_html', count: @api_token.allowed_procedures.size)
|
||||
|
||||
- if @api_token.allowed_procedures.empty?
|
||||
= button_to "Autoriser l’accès a toutes les démarches", @api_token, method: :patch, params: { api_token: { disallow_procedure_id: '0' } }, class: "fr-btn fr-btn--secondary"
|
||||
- else
|
||||
%ul
|
||||
- @api_token.allowed_procedures.each do |procedure|
|
||||
%li.flex.justify-between.align-center
|
||||
.truncate-80
|
||||
= "#{procedure.id} – #{procedure.libelle}"
|
||||
= button_to "Supprimer", @api_token, method: :patch, params: { api_token: { disallow_procedure_id: procedure.id } }, class: "fr-btn fr-btn--secondary"
|
||||
|
||||
.fr-card__end
|
||||
= form_for @api_token, namespace: dom_id(@api_token, :allowed_procedures), html: { class: 'form form-ds-fr-white mb-3', data: { turbo: true } } do |f|
|
||||
= f.label :allowed_procedure_ids do
|
||||
Autoriser l’accès seulement a des démarches choisies
|
||||
- @api_token.allowed_procedures.each do |procedure|
|
||||
= f.hidden_field :allowed_procedure_ids, value: procedure.id, multiple: true, id: dom_id(procedure, :allowed_procedure)
|
||||
.flex.justify-between.align-center{ 'data-turbo-force': true }
|
||||
= f.select :allowed_procedure_ids, procedures_to_allow_options, procedures_to_allow_select_options, { class: 'no-margin width-66 small', name: "api_token[allowed_procedure_ids][]" }
|
||||
= f.button type: :submit, class: "fr-btn fr-btn--secondary" do
|
||||
Ajouter
|
||||
|
||||
= form_for @api_token, namespace: dom_id(@api_token, :write_access), html: { class: 'form form-ds-fr-white mb-3', data: { turbo: true, controller: 'autosubmit' } } do |f|
|
||||
= f.label :write_access do
|
||||
Ce jeton a accès aux démarches
|
||||
%label.toggle-switch.no-margin
|
||||
= f.check_box :write_access, class: 'toggle-switch-checkbox'
|
||||
%span.toggle-switch-control.round
|
||||
%span.toggle-switch-label.on En lecture et écriture
|
||||
%span.toggle-switch-label.off En lecture seule
|
||||
|
||||
= button_to "Révoquer le jeton", api_token_path(@api_token), method: :delete, class: "fr-btn fr-btn--secondary", data: { turbo_confirm: "Confirmez-vous la révocation de ce jeton ? Les applications qui l’utilisent actuellement seront bloquées." }
|
||||
|
|
|
@ -10,13 +10,20 @@ class API::V2::BaseController < ApplicationController
|
|||
def context
|
||||
# new token
|
||||
if api_token.present?
|
||||
{ administrateur_id: api_token.administrateur.id }
|
||||
api_token.context
|
||||
# web interface (/graphql) give current_administrateur
|
||||
elsif current_administrateur.present?
|
||||
{ administrateur_id: current_administrateur.id }
|
||||
{
|
||||
administrateur_id: current_administrateur.id,
|
||||
procedure_ids: current_administrateur.procedure_ids,
|
||||
write_access: true
|
||||
}
|
||||
# old token
|
||||
else
|
||||
{ token: authorization_bearer_token }
|
||||
{
|
||||
token: authorization_bearer_token,
|
||||
write_access: true
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ class APIController < ApplicationController
|
|||
|
||||
def find_administrateur_for_token(procedure)
|
||||
api_token = APIToken.find_and_verify(authorization_bearer_token, procedure.administrateurs)
|
||||
if api_token.present? && procedure.administrateurs.include?(api_token.administrateur)
|
||||
if api_token.present? && api_token.context.fetch(:procedure_ids).include?(procedure.id)
|
||||
api_token.administrateur
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,7 +12,13 @@ class APITokensController < ApplicationController
|
|||
|
||||
def update
|
||||
@api_token = current_administrateur.api_tokens.find(params[:id])
|
||||
@api_token.update!(api_token_params)
|
||||
|
||||
disallow_procedure_id = api_token_params.fetch(:disallow_procedure_id, nil)
|
||||
if disallow_procedure_id.present?
|
||||
@api_token.disallow_procedure(disallow_procedure_id.to_i)
|
||||
else
|
||||
@api_token.update!(api_token_params)
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.turbo_stream { render :index }
|
||||
|
@ -33,6 +39,6 @@ class APITokensController < ApplicationController
|
|||
private
|
||||
|
||||
def api_token_params
|
||||
params.require(:api_token).permit(:name)
|
||||
params.require(:api_token).permit(:name, :write_access, :disallow_procedure_id, allowed_procedure_ids: [])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -26,6 +26,10 @@ class API::V2::Context < GraphQL::Query::Context
|
|||
self[:internal_use]
|
||||
end
|
||||
|
||||
def write_access?
|
||||
self[:write_access]
|
||||
end
|
||||
|
||||
def current_administrateur
|
||||
unless self[:administrateur_id]
|
||||
raise GraphQL::ExecutionError.new("Pour effectuer cette opération, vous avez besoin d’un jeton au nouveau format. Vous pouvez l’obtenir dans votre interface administrateur.", extensions: { code: :deprecated_token })
|
||||
|
@ -45,16 +49,25 @@ class API::V2::Context < GraphQL::Query::Context
|
|||
self[:authorized] ||= {}
|
||||
|
||||
if self[:authorized][demarche.id].nil?
|
||||
self[:authorized][demarche.id] = if self[:administrateur_id]
|
||||
demarche.administrateurs.map(&:id).include?(self[:administrateur_id])
|
||||
elsif self[:token]
|
||||
APIToken.find_and_verify(self[:token], demarche.administrateurs).present?
|
||||
end
|
||||
self[:authorized][demarche.id] = compute_demarche_authorization(demarche)
|
||||
end
|
||||
|
||||
self[:authorized][demarche.id]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def compute_demarche_authorization(demarche)
|
||||
# procedure_ids and token are passed from graphql controller
|
||||
if self[:procedure_ids].present?
|
||||
self[:procedure_ids].include?(demarche.id)
|
||||
elsif self[:token].present?
|
||||
APIToken.find_and_verify(self[:token], demarche.administrateurs).present?
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# This is a query AST visitor that we use to check
|
||||
# if a fragment with a given name is used in the given document.
|
||||
# We check for both inline and standalone fragments.
|
||||
|
|
|
@ -4,6 +4,18 @@ module Mutations
|
|||
|
||||
delegate :current_administrateur, to: :context
|
||||
|
||||
def ready?(**args)
|
||||
if context.write_access?
|
||||
authorized_before_load?(**args)
|
||||
else
|
||||
return false, { errors: ['Le jeton utilisé est configuré seulement en lecture'] }
|
||||
end
|
||||
end
|
||||
|
||||
def authorized_before_load?(**args)
|
||||
true
|
||||
end
|
||||
|
||||
def partition_instructeurs_by(instructeurs)
|
||||
instructeurs
|
||||
.partition { _1.id.present? }
|
||||
|
|
|
@ -19,7 +19,7 @@ module Mutations
|
|||
{ dossier: }
|
||||
end
|
||||
|
||||
def ready?(justificatif: nil, **args)
|
||||
def authorized_before_load?(justificatif: nil, **args)
|
||||
if justificatif.present?
|
||||
validate_blob(justificatif)
|
||||
else
|
||||
|
|
|
@ -19,7 +19,7 @@ module Mutations
|
|||
{ dossier: }
|
||||
end
|
||||
|
||||
def ready?(justificatif: nil, **args)
|
||||
def authorized_before_load?(justificatif: nil, **args)
|
||||
if justificatif.present?
|
||||
validate_blob(justificatif)
|
||||
else
|
||||
|
|
|
@ -20,7 +20,7 @@ module Mutations
|
|||
end
|
||||
end
|
||||
|
||||
def ready?(attachment: nil, **args)
|
||||
def authorized_before_load?(attachment: nil, **args)
|
||||
if attachment.present?
|
||||
validate_blob(attachment)
|
||||
else
|
||||
|
|
|
@ -19,7 +19,7 @@ module Mutations
|
|||
{ dossier: }
|
||||
end
|
||||
|
||||
def ready?(justificatif: nil, **args)
|
||||
def authorized_before_load?(justificatif: nil, **args)
|
||||
if justificatif.present?
|
||||
validate_blob(justificatif)
|
||||
else
|
||||
|
|
|
@ -2,18 +2,57 @@
|
|||
#
|
||||
# Table name: api_tokens
|
||||
#
|
||||
# id :uuid not null, primary key
|
||||
# encrypted_token :string not null
|
||||
# name :string not null
|
||||
# version :integer default(3), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# administrateur_id :bigint not null
|
||||
# id :uuid not null, primary key
|
||||
# allowed_procedure_ids :bigint is an Array
|
||||
# encrypted_token :string not null
|
||||
# name :string not null
|
||||
# write_access :boolean default(TRUE), not null
|
||||
# version :integer default(3), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# administrateur_id :bigint not null
|
||||
#
|
||||
class APIToken < ApplicationRecord
|
||||
include ActiveRecord::SecureToken
|
||||
|
||||
belongs_to :administrateur, inverse_of: :api_tokens
|
||||
has_many :procedures, through: :administrateur
|
||||
|
||||
before_save :check_allowed_procedure_ids_ownership
|
||||
|
||||
def context
|
||||
context = { administrateur_id: administrateur_id, write_access: write_access? }
|
||||
|
||||
if full_access?
|
||||
context.merge procedure_ids:
|
||||
else
|
||||
context.merge procedure_ids: procedure_ids & allowed_procedure_ids
|
||||
end
|
||||
end
|
||||
|
||||
def full_access?
|
||||
allowed_procedure_ids.nil?
|
||||
end
|
||||
|
||||
def procedures_to_allow
|
||||
procedures.select(:id, :libelle, :path).where.not(id: allowed_procedure_ids || []).order(:libelle)
|
||||
end
|
||||
|
||||
def allowed_procedures
|
||||
if allowed_procedure_ids.present?
|
||||
procedures.select(:id, :libelle, :path).where(id: allowed_procedure_ids).order(:libelle)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def disallow_procedure(procedure_id)
|
||||
allowed_procedure_ids = allowed_procedures.map(&:id) - [procedure_id]
|
||||
if allowed_procedure_ids.empty?
|
||||
allowed_procedure_ids = nil
|
||||
end
|
||||
update!(allowed_procedure_ids:)
|
||||
end
|
||||
|
||||
# Prefix is made of the first 6 characters of the uuid base64 encoded
|
||||
# it does not leak plain token
|
||||
|
@ -71,4 +110,12 @@ class APIToken < ApplicationRecord
|
|||
-> (api_token) { api_token if BCrypt::Password.new(api_token.encrypted_token) == plain_token }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_allowed_procedure_ids_ownership
|
||||
if allowed_procedure_ids.present?
|
||||
self.allowed_procedure_ids = allowed_procedures.map(&:id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
class AddReadOnlyAndProcedureIdsToAPITokens < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
add_column :api_tokens, :write_access, :boolean, default: true, null: false
|
||||
add_column :api_tokens, :allowed_procedure_ids, :bigint, array: true, null: true
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2023_02_17_094119) do
|
||||
ActiveRecord::Schema.define(version: 2023_02_18_094119) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pgcrypto"
|
||||
|
@ -89,9 +89,11 @@ ActiveRecord::Schema.define(version: 2023_02_17_094119) do
|
|||
|
||||
create_table "api_tokens", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.bigint "administrateur_id", null: false
|
||||
t.bigint "allowed_procedure_ids", array: true
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.string "encrypted_token", null: false
|
||||
t.string "name", null: false
|
||||
t.boolean "write_access", default: true, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.integer "version", default: 3, null: false
|
||||
t.index ["administrateur_id"], name: "index_api_tokens_on_administrateur_id"
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
describe API::V2::GraphqlController do
|
||||
let(:admin) { create(:administrateur) }
|
||||
let(:token) { APIToken.generate(admin)[1] }
|
||||
let(:generated_token) { APIToken.generate(admin) }
|
||||
let(:api_token) { generated_token.first }
|
||||
let(:token) { generated_token.second }
|
||||
let(:legacy_token) { APIToken.send(:unpack, token)[:plain_token] }
|
||||
let(:procedure) { create(:procedure, :published, :for_individual, :with_service, administrateurs: [admin]) }
|
||||
let(:dossier) { create(:dossier, :en_construction, :with_individual, procedure: procedure) }
|
||||
|
@ -193,6 +195,15 @@ describe API::V2::GraphqlController do
|
|||
expect(gql_errors.first[:message]).to eq("An object of type Demarche was hidden due to permissions")
|
||||
}
|
||||
end
|
||||
|
||||
context 'when procedure is not selected' do
|
||||
let(:other_procedure) { create(:procedure, administrateurs: [admin]) }
|
||||
before { api_token.update(allowed_procedure_ids: [other_procedure.id]) }
|
||||
|
||||
it {
|
||||
expect(gql_errors.first[:message]).to eq("An object of type Demarche was hidden due to permissions")
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
describe "demarche" do
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
describe API::V2::GraphqlController do
|
||||
let(:admin) { create(:administrateur) }
|
||||
let(:token) { APIToken.generate(admin)[1] }
|
||||
let(:generated_token) { APIToken.generate(admin) }
|
||||
let(:api_token) { generated_token.first }
|
||||
let(:token) { generated_token.second }
|
||||
let(:legacy_token) { APIToken.send(:unpack, token)[:plain_token] }
|
||||
let(:procedure) { create(:procedure, :published, :for_individual, :with_service, administrateurs: [admin], types_de_champ_public:) }
|
||||
let(:types_de_champ_public) { [] }
|
||||
|
@ -210,6 +212,14 @@ describe API::V2::GraphqlController do
|
|||
expect(gql_data[:dossierArchiver][:dossier][:id]).to eq(dossier.to_typed_id)
|
||||
expect(gql_data[:dossierArchiver][:dossier][:archived]).to be_truthy
|
||||
}
|
||||
|
||||
context 'read only token' do
|
||||
before { api_token.update(write_access: false) }
|
||||
|
||||
it {
|
||||
expect(gql_data[:dossierArchiver][:errors].first[:message]).to eq('Le jeton utilisé est configuré seulement en lecture')
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
context 'dossierPasserEnInstruction' do
|
||||
|
@ -262,6 +272,14 @@ describe API::V2::GraphqlController do
|
|||
expect(gql_data[:dossierAccepter][:dossier][:id]).to eq(dossier.to_typed_id)
|
||||
expect(gql_data[:dossierAccepter][:dossier][:state]).to eq('accepte')
|
||||
}
|
||||
|
||||
context 'read only token' do
|
||||
before { api_token.update(write_access: false) }
|
||||
|
||||
it {
|
||||
expect(gql_data[:dossierAccepter][:errors].first[:message]).to eq('Le jeton utilisé est configuré seulement en lecture')
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
context 'dossierRefuser' do
|
||||
|
@ -275,6 +293,14 @@ describe API::V2::GraphqlController do
|
|||
expect(gql_data[:dossierRefuser][:dossier][:id]).to eq(dossier.to_typed_id)
|
||||
expect(gql_data[:dossierRefuser][:dossier][:state]).to eq('refuse')
|
||||
}
|
||||
|
||||
context 'read only token' do
|
||||
before { api_token.update(write_access: false) }
|
||||
|
||||
it {
|
||||
expect(gql_data[:dossierRefuser][:errors].first[:message]).to eq('Le jeton utilisé est configuré seulement en lecture')
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
context 'dossierClasserSansSuite' do
|
||||
|
@ -288,6 +314,14 @@ describe API::V2::GraphqlController do
|
|||
expect(gql_data[:dossierClasserSansSuite][:dossier][:id]).to eq(dossier.to_typed_id)
|
||||
expect(gql_data[:dossierClasserSansSuite][:dossier][:state]).to eq('sans_suite')
|
||||
}
|
||||
|
||||
context 'read only token' do
|
||||
before { api_token.update(write_access: false) }
|
||||
|
||||
it {
|
||||
expect(gql_data[:dossierClasserSansSuite][:errors].first[:message]).to eq('Le jeton utilisé est configuré seulement en lecture')
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
context 'groupeInstructeurModifier' do
|
||||
|
|
|
@ -5,7 +5,7 @@ RSpec.describe Mutations::DossierModifierAnnotation, type: :graphql do
|
|||
let(:instructeur) { create(:instructeur, followed_dossiers: dossiers) }
|
||||
|
||||
let(:query) { '' }
|
||||
let(:context) { { administrateur_id: admin.id } }
|
||||
let(:context) { { administrateur_id: admin.id, procedure_ids: admin.procedure_ids, write_access: true } }
|
||||
let(:variables) { {} }
|
||||
|
||||
subject { API::V2::Schema.execute(query, variables: variables, context: context) }
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
RSpec.describe Types::DemarcheType, type: :graphql do
|
||||
let(:admin) { create(:administrateur) }
|
||||
let(:query) { '' }
|
||||
let(:context) { { internal_use: true } }
|
||||
let(:context) { { procedure_ids: admin.procedure_ids } }
|
||||
let(:variables) { {} }
|
||||
|
||||
subject { API::V2::Schema.execute(query, variables: variables, context: context) }
|
||||
|
@ -10,11 +11,9 @@ RSpec.describe Types::DemarcheType, type: :graphql do
|
|||
|
||||
describe 'context should correctly preserve demarche authorization state' do
|
||||
let(:query) { DEMARCHE_QUERY }
|
||||
let(:admin) { create(:administrateur) }
|
||||
let(:procedure) { create(:procedure, administrateurs: [admin]) }
|
||||
|
||||
let(:other_admin_procedure) { create(:procedure) }
|
||||
let(:context) { { administrateur_id: admin.id } }
|
||||
let(:variables) { { number: procedure.id } }
|
||||
|
||||
it do
|
||||
|
@ -27,8 +26,8 @@ RSpec.describe Types::DemarcheType, type: :graphql do
|
|||
end
|
||||
|
||||
describe 'demarche with clone' do
|
||||
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :yes_no }]) }
|
||||
let(:procedure_clone) { procedure.clone(procedure.administrateurs.first, false) }
|
||||
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :yes_no }], administrateurs: [admin]) }
|
||||
let(:procedure_clone) { procedure.clone(admin, false) }
|
||||
let(:query) { DEMARCHE_WITH_CHAMP_DESCRIPTORS_QUERY }
|
||||
let(:variables) { { number: procedure_clone.id } }
|
||||
let(:champ_descriptor_id) { procedure.draft_revision.types_de_champ_public.first.to_typed_id }
|
||||
|
|
|
@ -11,6 +11,100 @@ describe APIToken, type: :model do
|
|||
expect(api_token.administrateur).to eq(administrateur)
|
||||
expect(api_token.prefix).to eq(packed_token.slice(0, 5))
|
||||
expect(api_token.version).to eq(3)
|
||||
expect(api_token.write_access?).to eq(true)
|
||||
expect(api_token.procedure_ids).to eq([])
|
||||
expect(api_token.allowed_procedure_ids).to eq(nil)
|
||||
expect(api_token.context).to eq(administrateur_id: administrateur.id, procedure_ids: [], write_access: true)
|
||||
expect(api_token.full_access?).to be_truthy
|
||||
end
|
||||
|
||||
context 'with read_only' do
|
||||
before { api_token.update(write_access: false) }
|
||||
it do
|
||||
expect(api_token.full_access?).to be_truthy
|
||||
expect(api_token.context).to eq(administrateur_id: administrateur.id, procedure_ids: [], write_access: false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with procedure' do
|
||||
let(:procedure) { create(:procedure, administrateurs: [administrateur]) }
|
||||
before { procedure }
|
||||
|
||||
it do
|
||||
expect(api_token.procedure_ids).to eq([procedure.id])
|
||||
expect(api_token.procedures_to_allow).to eq([procedure])
|
||||
expect(api_token.allowed_procedure_ids).to eq(nil)
|
||||
expect(api_token.context).to eq(administrateur_id: administrateur.id, procedure_ids: [procedure.id], write_access: true)
|
||||
end
|
||||
|
||||
context 'update with procedure_id' do
|
||||
let(:procedure) { create(:procedure, administrateurs: [administrateur]) }
|
||||
let(:other_procedure) { create(:procedure, administrateurs: [administrateur]) }
|
||||
before { api_token.update(allowed_procedure_ids: [procedure.id]); other_procedure }
|
||||
|
||||
it do
|
||||
expect(api_token.procedure_ids).to match_array([procedure.id, other_procedure.id])
|
||||
expect(api_token.procedures_to_allow).to eq([other_procedure])
|
||||
expect(api_token.allowed_procedure_ids).to eq([procedure.id])
|
||||
expect(api_token.context).to eq(administrateur_id: administrateur.id, procedure_ids: [procedure.id], write_access: true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'update with wrong procedure_id' do
|
||||
let(:other_administrateur) { create(:administrateur) }
|
||||
let(:procedure) { create(:procedure, administrateurs: [other_administrateur]) }
|
||||
before { api_token.update(allowed_procedure_ids: [procedure.id]) }
|
||||
|
||||
it do
|
||||
expect(api_token.full_access?).to be_falsey
|
||||
expect(api_token.procedure_ids).to eq([])
|
||||
expect(api_token.procedures_to_allow).to eq([])
|
||||
expect(api_token.allowed_procedure_ids).to eq([])
|
||||
expect(api_token.context).to eq(administrateur_id: administrateur.id, procedure_ids: [], write_access: true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'update with destroyed procedure_id' do
|
||||
let(:procedure) { create(:procedure, administrateurs: [administrateur]) }
|
||||
before { api_token.update(allowed_procedure_ids: [procedure.id]); procedure.destroy }
|
||||
|
||||
it do
|
||||
expect(api_token.full_access?).to be_falsey
|
||||
expect(api_token.procedure_ids).to eq([])
|
||||
expect(api_token.procedures_to_allow).to eq([])
|
||||
expect(api_token.allowed_procedure_ids).to eq([procedure.id])
|
||||
expect(api_token.context).to eq(administrateur_id: administrateur.id, procedure_ids: [], write_access: true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'update with detached procedure_id' do
|
||||
let(:other_procedure) { create(:procedure, administrateurs: [administrateur]) }
|
||||
let(:procedure) { create(:procedure, administrateurs: [administrateur]) }
|
||||
before { api_token.update(allowed_procedure_ids: [procedure.id]); other_procedure; administrateur.procedures.delete(procedure) }
|
||||
|
||||
it do
|
||||
expect(api_token.full_access?).to be_falsey
|
||||
expect(api_token.procedure_ids).to eq([other_procedure.id])
|
||||
expect(api_token.allowed_procedure_ids).to eq([procedure.id])
|
||||
expect(api_token.context).to eq(administrateur_id: administrateur.id, procedure_ids: [], write_access: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with procedure and allowed_procedure_ids' do
|
||||
let(:procedure) { create(:procedure, administrateurs: [administrateur]) }
|
||||
let(:other_procedure) { create(:procedure, administrateurs: [administrateur]) }
|
||||
|
||||
before do
|
||||
api_token.update(allowed_procedure_ids: [procedure.id])
|
||||
other_procedure
|
||||
end
|
||||
|
||||
it do
|
||||
expect(api_token.procedure_ids).to eq([procedure.id, other_procedure.id])
|
||||
expect(api_token.allowed_procedure_ids).to eq([procedure.id])
|
||||
expect(api_token.context).to eq(administrateur_id: administrateur.id, procedure_ids: [procedure.id], write_access: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue