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 include ActiveRecord::SecureToken
belongs_to :administrateur, inverse_of: :api_tokens 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 def context
context = { administrateur_id: administrateur_id, write_access: write_access? } {
administrateur_id:,
procedure_ids:,
write_access:
}
end
def procedure_ids
if full_access? if full_access?
context.merge procedure_ids: administrateur.procedures.ids
else else
context.merge procedure_ids: procedure_ids & allowed_procedure_ids sanitized_targeted_procedure_ids
end end
end end
def procedures
Procedure.where(id: procedure_ids)
end
def full_access? def full_access?
allowed_procedure_ids.nil? targeted_procedure_ids.nil?
end end
def procedures_to_allow def targetable_procedures
procedures.select(:id, :libelle, :path).where.not(id: allowed_procedure_ids || []).order(:libelle) administrateur
.procedures
.where.not(id: targeted_procedure_ids)
.select(:id, :libelle, :path)
.order(:libelle)
end end
def allowed_procedures def untarget_procedure(procedure_id)
if allowed_procedure_ids.present? new_target_ids = targeted_procedure_ids - [procedure_id]
procedures.select(:id, :libelle, :path).where(id: allowed_procedure_ids).order(:libelle)
else update!(allowed_procedure_ids: new_target_ids)
[]
end
end end
def disallow_procedure(procedure_id) def sanitized_targeted_procedure_ids
allowed_procedure_ids = allowed_procedures.map(&:id) - [procedure_id] administrateur.procedures.ids.intersection(targeted_procedure_ids || [])
if allowed_procedure_ids.empty? end
allowed_procedure_ids = nil
end def become_full_access!
update!(allowed_procedure_ids:) update_column(:allowed_procedure_ids, nil)
end end
# Prefix is made of the first 6 characters of the uuid base64 encoded # Prefix is made of the first 6 characters of the uuid base64 encoded
@ -70,12 +81,16 @@ class APIToken < ApplicationRecord
private private
def check_allowed_procedure_ids_ownership def sanitize_targeted_procedure_ids
if allowed_procedure_ids.present? if targeted_procedure_ids.present?
self.allowed_procedure_ids = allowed_procedures.map(&:id) write_attribute(:allowed_procedure_ids, sanitized_targeted_procedure_ids)
end end
end end
def targeted_procedure_ids
read_attribute(:allowed_procedure_ids)
end
class BearerToken < Data.define(:api_token_id, :plain_token) class BearerToken < Data.define(:api_token_id, :plain_token)
def to_string def to_string
Base64.urlsafe_encode64([api_token_id, plain_token].join(';')) 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(:api_token) { api_token_and_packed_token.first }
let(:packed_token) { api_token_and_packed_token.second } 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.administrateur).to eq(administrateur)
expect(api_token.prefix).to eq(packed_token.slice(0, 5)) expect(api_token.prefix).to eq(packed_token.slice(0, 5))
expect(api_token.version).to eq(3) expect(api_token.version).to eq(3)
expect(api_token.write_access?).to eq(true) expect(api_token.write_access?).to eq(true)
expect(api_token.procedure_ids).to eq([]) 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.context).to eq(administrateur_id: administrateur.id, procedure_ids: [], write_access: true)
expect(api_token.full_access?).to be_truthy expect(api_token.full_access?).to be_truthy
end end
context 'with read_only' do context 'updated read_only' do
before { api_token.update(write_access: false) } before { api_token.update(write_access: false) }
it do 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) expect(api_token.context).to eq(administrateur_id: administrateur.id, procedure_ids: [], write_access: false)
end end
end end
context 'with procedure' do context 'with a new added procedure' do
let(:procedure) { create(:procedure, administrateurs: [administrateur]) } let(:procedure) { create(:procedure, administrateurs: [administrateur]) }
before { procedure }
before do
procedure
api_token.reload
end
it do it do
expect(api_token.full_access?).to be_truthy
expect(api_token.procedure_ids).to eq([procedure.id]) expect(api_token.procedure_ids).to eq([procedure.id])
expect(api_token.procedures_to_allow).to eq([procedure]) expect(api_token.procedures).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) expect(api_token.context).to eq(administrateur_id: administrateur.id, procedure_ids: [procedure.id], write_access: true)
end end
context 'update with procedure_id' do context 'and another procedure, but access only to the first one' do
let(:procedure) { create(:procedure, administrateurs: [administrateur]) }
let(:other_procedure) { create(:procedure, administrateurs: [administrateur]) } 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 it do
expect(api_token.procedure_ids).to match_array([procedure.id, other_procedure.id]) expect(api_token.full_access?).to be_falsey
expect(api_token.procedures_to_allow).to eq([other_procedure]) expect(api_token.procedure_ids).to match_array([procedure.id])
expect(api_token.allowed_procedure_ids).to eq([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) expect(api_token.context).to eq(administrateur_id: administrateur.id, procedure_ids: [procedure.id], write_access: true)
end 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 end
context 'update with wrong procedure_id' do context 'but acces to a wrong procedure_id' do
let(:other_administrateur) { create(:administrateur) } let(:forbidden_procedure) { create(:procedure) }
let(:procedure) { create(:procedure, administrateurs: [other_administrateur]) }
before { api_token.update(allowed_procedure_ids: [procedure.id]) } before do
api_token.update(allowed_procedure_ids: [forbidden_procedure.id])
api_token.reload
end
it do it do
expect(api_token.full_access?).to be_falsey expect(api_token.full_access?).to be_falsey
expect(api_token.procedure_ids).to eq([]) expect(api_token.procedure_ids).to eq([])
expect(api_token.procedures_to_allow).to eq([]) expect(api_token.targetable_procedures).to eq([procedure])
expect(api_token.allowed_procedure_ids).to eq([])
expect(api_token.context).to eq(administrateur_id: administrateur.id, procedure_ids: [], write_access: true) expect(api_token.context).to eq(administrateur_id: administrateur.id, procedure_ids: [], write_access: true)
end end
end end
context 'update with destroyed procedure_id' do context 'update with destroyed procedure_id' do
let(:procedure) { create(:procedure, administrateurs: [administrateur]) } 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 it do
expect(api_token.full_access?).to be_falsey expect(api_token.full_access?).to be_falsey
expect(api_token.procedure_ids).to eq([]) expect(api_token.procedure_ids).to eq([])
expect(api_token.procedures_to_allow).to eq([]) expect(api_token.targetable_procedures).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) expect(api_token.context).to eq(administrateur_id: administrateur.id, procedure_ids: [], write_access: true)
end end
end end
context 'update with detached procedure_id' do context 'update with detached procedure_id' do
let(:other_procedure) { create(:procedure, administrateurs: [administrateur]) }
let(: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 it do
expect(api_token.full_access?).to be_falsey expect(api_token.full_access?).to be_falsey
expect(api_token.procedure_ids).to eq([other_procedure.id]) expect(api_token.procedure_ids).to eq([])
expect(api_token.allowed_procedure_ids).to eq([procedure.id]) expect(api_token.targetable_procedures).to eq([other_procedure])
expect(api_token.context).to eq(administrateur_id: administrateur.id, procedure_ids: [], write_access: true) expect(api_token.context).to eq(administrateur_id: administrateur.id, procedure_ids: [], write_access: true)
end end
end 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 end
describe '#authenticate' do describe '#authenticate' do
@ -133,8 +151,7 @@ describe APIToken, type: :model do
context "with a bearer token with the wrong plain_token" do context "with a bearer token with the wrong plain_token" do
let(:bearer_token) do let(:bearer_token) do
clear_packed = [api_token.id, 'wrong'].join(';') APIToken::BearerToken.new(api_token.id, 'wrong').to_string
Base64.urlsafe_encode64(clear_packed)
end end
it { is_expected.to be_nil } it { is_expected.to be_nil }