demarches-normaliennes/app/models/api_token.rb

121 lines
3.9 KiB
Ruby
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# == Schema Information
#
# Table name: api_tokens
#
# 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
def prefix
Base64.urlsafe_encode64(id).slice(0, 5)
end
class << self
def generate(administrateur)
plain_token = generate_unique_secure_token
encrypted_token = BCrypt::Password.create(plain_token)
api_token = create!(administrateur:, encrypted_token:, name: Date.today.strftime('Jeton dAPI généré le %d/%m/%Y'))
packed_token = Base64.urlsafe_encode64([api_token.id, plain_token].join(';'))
[api_token, packed_token]
end
def find_and_verify(maybe_packed_token, administrateurs = [])
case unpack(maybe_packed_token)
in { plain_token:, id: } # token v3
find_by(id:, version: 3)&.then(&ensure_valid_token(plain_token))
in { plain_token:, administrateur_id: } # token v2
# the migration to the APIToken model set `version: 1` for all the v1 and v2 token
# this is the only place where we can fix the version
where(administrateur_id:, version: 1).update_all(version: 2) # update to v2
find_by(administrateur_id:, version: 2)&.then(&ensure_valid_token(plain_token))
in { plain_token: } # token v1
where(administrateur: administrateurs, version: 1).find(&ensure_valid_token(plain_token))
end
end
private
UUID_SIZE = SecureRandom.uuid.size
def unpack(maybe_packed_token)
case message_verifier.verified(maybe_packed_token)
in [administrateur_id, plain_token]
{ plain_token:, administrateur_id: }
else
case Base64.urlsafe_decode64(maybe_packed_token).split(';')
in [id, plain_token] if id.size == UUID_SIZE # valid format "<uuid>;<random token>"
{ plain_token:, id: }
else
{ plain_token: maybe_packed_token }
end
end
rescue
{ plain_token: maybe_packed_token }
end
def message_verifier
Rails.application.message_verifier('api_v2_token')
end
def ensure_valid_token(plain_token)
-> (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