2024-04-29 00:17:15 +02:00
|
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
2022-11-30 10:06:33 +01:00
|
|
|
|
class APIToken < ApplicationRecord
|
|
|
|
|
include ActiveRecord::SecureToken
|
2022-09-28 12:40:44 +02:00
|
|
|
|
|
2022-11-30 10:06:33 +01:00
|
|
|
|
belongs_to :administrateur, inverse_of: :api_tokens
|
2023-02-13 14:57:51 +01:00
|
|
|
|
|
2024-01-23 17:20:53 +01:00
|
|
|
|
scope :expiring_within, -> (duration) { where(expires_at: Date.today..duration.from_now) }
|
|
|
|
|
|
|
|
|
|
scope :without_any_expiration_notice_sent_within, -> (duration) do
|
|
|
|
|
where.not('(expires_at - (?::interval)) <= some(expiration_notices_sent_at)', duration.iso8601)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
scope :with_a_bigger_lifetime_than, -> (duration) do
|
|
|
|
|
where('? < expires_at - created_at', duration.iso8601)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
scope :with_expiration_notice_to_send_for, -> (duration) do
|
|
|
|
|
# example for duration = 1.month
|
|
|
|
|
# take all tokens that expire in the next month
|
|
|
|
|
# with a lifetime bigger than 1 month
|
|
|
|
|
# without any expiration notice sent for that period
|
|
|
|
|
expiring_within(duration)
|
|
|
|
|
.with_a_bigger_lifetime_than(duration)
|
|
|
|
|
.without_any_expiration_notice_sent_within(duration)
|
|
|
|
|
end
|
|
|
|
|
|
2023-08-03 17:53:34 +02:00
|
|
|
|
before_save :sanitize_targeted_procedure_ids
|
2023-02-28 16:33:22 +01:00
|
|
|
|
|
2023-02-13 14:57:51 +01:00
|
|
|
|
def context
|
2023-08-03 17:53:34 +02:00
|
|
|
|
{
|
|
|
|
|
administrateur_id:,
|
2023-11-08 11:21:45 +01:00
|
|
|
|
api_token_id: id,
|
2023-08-03 17:53:34 +02:00
|
|
|
|
procedure_ids:,
|
|
|
|
|
write_access:
|
|
|
|
|
}
|
|
|
|
|
end
|
2023-02-13 14:57:51 +01:00
|
|
|
|
|
2023-08-03 17:53:34 +02:00
|
|
|
|
def procedure_ids
|
2023-02-13 14:57:51 +01:00
|
|
|
|
if full_access?
|
2023-08-03 17:53:34 +02:00
|
|
|
|
administrateur.procedures.ids
|
2023-02-13 14:57:51 +01:00
|
|
|
|
else
|
2023-08-03 17:53:34 +02:00
|
|
|
|
sanitized_targeted_procedure_ids
|
2023-02-13 14:57:51 +01:00
|
|
|
|
end
|
|
|
|
|
end
|
2022-09-28 12:40:44 +02:00
|
|
|
|
|
2023-08-03 17:53:34 +02:00
|
|
|
|
def procedures
|
|
|
|
|
Procedure.where(id: procedure_ids)
|
|
|
|
|
end
|
|
|
|
|
|
2023-02-28 16:33:22 +01:00
|
|
|
|
def full_access?
|
2023-08-03 17:53:34 +02:00
|
|
|
|
targeted_procedure_ids.nil?
|
2023-02-28 16:33:22 +01:00
|
|
|
|
end
|
|
|
|
|
|
2023-08-03 17:53:34 +02:00
|
|
|
|
def targetable_procedures
|
|
|
|
|
administrateur
|
|
|
|
|
.procedures
|
|
|
|
|
.where.not(id: targeted_procedure_ids)
|
|
|
|
|
.select(:id, :libelle, :path)
|
|
|
|
|
.order(:libelle)
|
2023-02-28 16:33:22 +01:00
|
|
|
|
end
|
|
|
|
|
|
2023-08-03 17:53:34 +02:00
|
|
|
|
def sanitized_targeted_procedure_ids
|
|
|
|
|
administrateur.procedures.ids.intersection(targeted_procedure_ids || [])
|
|
|
|
|
end
|
|
|
|
|
|
2022-11-30 10:06:33 +01:00
|
|
|
|
# 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)
|
2022-09-28 12:40:44 +02:00
|
|
|
|
end
|
|
|
|
|
|
2023-12-21 15:58:03 +01:00
|
|
|
|
def store_new_ip(ip)
|
|
|
|
|
set = Set.new(stored_ips)
|
|
|
|
|
if set.add?(IPAddr.new(ip))
|
|
|
|
|
update!(stored_ips: set.to_a)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2024-01-17 10:44:09 +01:00
|
|
|
|
def authorized_networks_for_ui
|
|
|
|
|
authorized_networks.map { "#{_1.to_string}/#{_1.prefix}" }.join(', ')
|
|
|
|
|
end
|
|
|
|
|
|
2023-12-21 13:59:21 +01:00
|
|
|
|
def forbidden_network?(ip)
|
|
|
|
|
return false if authorized_networks.blank?
|
|
|
|
|
|
|
|
|
|
authorized_networks.none? { |range| range.include?(ip) }
|
|
|
|
|
end
|
|
|
|
|
|
2024-01-17 09:31:21 +01:00
|
|
|
|
def expired?
|
|
|
|
|
expires_at&.past?
|
|
|
|
|
end
|
|
|
|
|
|
2024-01-26 17:11:00 +01:00
|
|
|
|
def eternal?
|
|
|
|
|
expires_at.nil?
|
|
|
|
|
end
|
|
|
|
|
|
2022-11-30 10:06:33 +01:00
|
|
|
|
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 d’API généré le %d/%m/%Y'))
|
2023-08-03 12:04:08 +02:00
|
|
|
|
bearer = BearerToken.new(api_token.id, plain_token)
|
|
|
|
|
[api_token, bearer.to_string]
|
2022-11-30 10:06:33 +01:00
|
|
|
|
end
|
2022-09-28 12:40:44 +02:00
|
|
|
|
|
2023-08-03 15:27:33 +02:00
|
|
|
|
def authenticate(bearer_string)
|
2023-08-03 12:04:08 +02:00
|
|
|
|
bearer = BearerToken.from_string(bearer_string)
|
|
|
|
|
|
|
|
|
|
return if bearer.nil?
|
|
|
|
|
|
|
|
|
|
api_token = find_by(id: bearer.api_token_id, version: 3)
|
2022-11-30 10:06:33 +01:00
|
|
|
|
|
2023-08-03 12:04:08 +02:00
|
|
|
|
return if api_token.nil?
|
2022-11-30 10:06:33 +01:00
|
|
|
|
|
2023-08-03 12:04:08 +02:00
|
|
|
|
BCrypt::Password.new(api_token.encrypted_token) == bearer.plain_token ? api_token : nil
|
2022-11-30 10:06:33 +01:00
|
|
|
|
end
|
2022-09-28 12:40:44 +02:00
|
|
|
|
end
|
2023-02-28 16:33:22 +01:00
|
|
|
|
|
2024-01-17 10:44:09 +01:00
|
|
|
|
def last_used_at
|
|
|
|
|
last_v2_authenticated_at || last_v1_authenticated_at
|
|
|
|
|
end
|
|
|
|
|
|
2023-02-28 16:33:22 +01:00
|
|
|
|
private
|
|
|
|
|
|
2023-08-03 17:53:34 +02:00
|
|
|
|
def sanitize_targeted_procedure_ids
|
|
|
|
|
if targeted_procedure_ids.present?
|
|
|
|
|
write_attribute(:allowed_procedure_ids, sanitized_targeted_procedure_ids)
|
2023-02-28 16:33:22 +01:00
|
|
|
|
end
|
|
|
|
|
end
|
2023-08-03 12:04:08 +02:00
|
|
|
|
|
2023-08-03 17:53:34 +02:00
|
|
|
|
def targeted_procedure_ids
|
|
|
|
|
read_attribute(:allowed_procedure_ids)
|
|
|
|
|
end
|
|
|
|
|
|
2023-08-03 12:04:08 +02:00
|
|
|
|
class BearerToken < Data.define(:api_token_id, :plain_token)
|
|
|
|
|
def to_string
|
|
|
|
|
Base64.urlsafe_encode64([api_token_id, plain_token].join(';'))
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def self.from_string(bearer_token)
|
|
|
|
|
return if bearer_token.nil?
|
|
|
|
|
|
|
|
|
|
api_token_id, plain_token = Base64.urlsafe_decode64(bearer_token).split(';')
|
|
|
|
|
BearerToken.new(api_token_id, plain_token)
|
|
|
|
|
rescue ArgumentError
|
|
|
|
|
end
|
|
|
|
|
end
|
2022-09-28 12:40:44 +02:00
|
|
|
|
end
|