refactor of api_token

This commit is contained in:
simon lehericey 2023-08-03 17:53:34 +02:00
parent 24fd12ed70
commit 05a8fd8ee1
2 changed files with 100 additions and 68 deletions

View file

@ -2,42 +2,53 @@ 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
before_save :sanitize_targeted_procedure_ids
def context
context = { administrateur_id: administrateur_id, write_access: write_access? }
{
administrateur_id:,
procedure_ids:,
write_access:
}
end
def procedure_ids
if full_access?
context.merge procedure_ids:
administrateur.procedures.ids
else
context.merge procedure_ids: procedure_ids & allowed_procedure_ids
sanitized_targeted_procedure_ids
end
end
def procedures
Procedure.where(id: procedure_ids)
end
def full_access?
allowed_procedure_ids.nil?
targeted_procedure_ids.nil?
end
def procedures_to_allow
procedures.select(:id, :libelle, :path).where.not(id: allowed_procedure_ids || []).order(:libelle)
def targetable_procedures
administrateur
.procedures
.where.not(id: targeted_procedure_ids)
.select(:id, :libelle, :path)
.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
def untarget_procedure(procedure_id)
new_target_ids = targeted_procedure_ids - [procedure_id]
update!(allowed_procedure_ids: new_target_ids)
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:)
def sanitized_targeted_procedure_ids
administrateur.procedures.ids.intersection(targeted_procedure_ids || [])
end
def become_full_access!
update_column(:allowed_procedure_ids, nil)
end
# Prefix is made of the first 6 characters of the uuid base64 encoded
@ -70,12 +81,16 @@ class APIToken < ApplicationRecord
private
def check_allowed_procedure_ids_ownership
if allowed_procedure_ids.present?
self.allowed_procedure_ids = allowed_procedures.map(&:id)
def sanitize_targeted_procedure_ids
if targeted_procedure_ids.present?
write_attribute(:allowed_procedure_ids, sanitized_targeted_procedure_ids)
end
end
def targeted_procedure_ids
read_attribute(:allowed_procedure_ids)
end
class BearerToken < Data.define(:api_token_id, :plain_token)
def to_string
Base64.urlsafe_encode64([api_token_id, plain_token].join(';'))

View file

@ -6,105 +6,123 @@ describe APIToken, type: :model do
let(:api_token) { api_token_and_packed_token.first }
let(:packed_token) { api_token_and_packed_token.second }
it do
before { api_token_and_packed_token }
it 'with a full access token' 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
context 'updated 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
context 'with a new added procedure' do
let(:procedure) { create(:procedure, administrateurs: [administrateur]) }
before { procedure }
before do
procedure
api_token.reload
end
it do
expect(api_token.full_access?).to be_truthy
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.procedures).to eq([procedure])
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]) }
context 'and another procedure, but access only to the first one' do
let(:other_procedure) { create(:procedure, administrateurs: [administrateur]) }
before { api_token.update(allowed_procedure_ids: [procedure.id]); other_procedure }
before do
other_procedure
api_token.update(allowed_procedure_ids: [procedure.id])
api_token.reload
end
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.full_access?).to be_falsey
expect(api_token.procedure_ids).to match_array([procedure.id])
expect(api_token.targetable_procedures).to eq([other_procedure])
expect(api_token.context).to eq(administrateur_id: administrateur.id, procedure_ids: [procedure.id], write_access: true)
end
context 'and then gain full access' do
before do
api_token.become_full_access!
api_token.reload
end
it do
expect(api_token.full_access?).to be(true)
expect(api_token.procedure_ids).to match_array([procedure.id, other_procedure.id])
expect(api_token.targetable_procedures).to eq([procedure, other_procedure])
end
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]) }
context 'but acces to a wrong procedure_id' do
let(:forbidden_procedure) { create(:procedure) }
before do
api_token.update(allowed_procedure_ids: [forbidden_procedure.id])
api_token.reload
end
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.targetable_procedures).to eq([procedure])
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 }
before do
api_token.update(allowed_procedure_ids: [procedure.id])
procedure.destroy
api_token.reload
end
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.targetable_procedures).to eq([])
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) }
let(:other_procedure) { create(:procedure, administrateurs: [administrateur]) }
before do
api_token.update(allowed_procedure_ids: [procedure.id])
other_procedure
administrateur.procedures.delete(procedure)
api_token.reload
end
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.procedure_ids).to eq([])
expect(api_token.targetable_procedures).to eq([other_procedure])
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 match_array([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
describe '#authenticate' do
@ -133,8 +151,7 @@ describe APIToken, type: :model do
context "with a bearer token with the wrong plain_token" do
let(:bearer_token) do
clear_packed = [api_token.id, 'wrong'].join(';')
Base64.urlsafe_encode64(clear_packed)
APIToken::BearerToken.new(api_token.id, 'wrong').to_string
end
it { is_expected.to be_nil }