Add BillSignature Model
This commit is contained in:
parent
dace9a53d3
commit
f355f849a6
9 changed files with 302 additions and 1 deletions
84
app/models/bill_signature.rb
Normal file
84
app/models/bill_signature.rb
Normal file
|
@ -0,0 +1,84 @@
|
|||
class BillSignature < ApplicationRecord
|
||||
has_many :dossier_operation_logs
|
||||
|
||||
has_one_attached :serialized
|
||||
has_one_attached :signature
|
||||
|
||||
validate :check_bill_digest
|
||||
validate :check_serialized_bill_contents
|
||||
validate :check_signature_contents
|
||||
|
||||
def self.build_with_operations(operations, day)
|
||||
bill = new(dossier_operation_logs: operations)
|
||||
|
||||
bill.serialize_operations(day)
|
||||
|
||||
bill
|
||||
end
|
||||
|
||||
def serialize_operations(day)
|
||||
self.serialized.attach(
|
||||
io: StringIO.new(operations_bill_json),
|
||||
filename: "demarches-simplifiees-operations-#{day.to_date.iso8601}.json",
|
||||
content_type: 'application/json',
|
||||
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE }
|
||||
)
|
||||
|
||||
self.digest = operations_bill_digest
|
||||
end
|
||||
|
||||
def operations_bill
|
||||
dossier_operation_logs.map { |op| [op.id.to_s, op.digest] }.to_h
|
||||
end
|
||||
|
||||
def operations_bill_json
|
||||
operations_bill.to_json
|
||||
end
|
||||
|
||||
def operations_bill_digest
|
||||
Digest::SHA256.hexdigest(operations_bill_json)
|
||||
end
|
||||
|
||||
def set_signature(signature, day)
|
||||
self.signature.attach(
|
||||
io: StringIO.new(signature),
|
||||
filename: "demarches-simplifiees-signature-#{day.to_date.iso8601}.der",
|
||||
content_type: 'application/x-x509-ca-cert'
|
||||
)
|
||||
end
|
||||
|
||||
# Validations
|
||||
def check_bill_digest
|
||||
if self.digest != self.operations_bill_digest
|
||||
errors.add(:digest)
|
||||
end
|
||||
end
|
||||
|
||||
def check_serialized_bill_contents
|
||||
if !self.serialized.attached?
|
||||
errors.add(:serialized, :blank)
|
||||
return
|
||||
end
|
||||
|
||||
if JSON.parse(self.serialized.download) != self.operations_bill
|
||||
errors.add(:serialized)
|
||||
end
|
||||
end
|
||||
|
||||
def check_signature_contents
|
||||
if !self.signature.attached?
|
||||
errors.add(:signature, :blank)
|
||||
return
|
||||
end
|
||||
|
||||
timestamp_signature_date = ASN1::Timestamp.signature_time(self.signature.download)
|
||||
if timestamp_signature_date > Time.zone.now
|
||||
errors.add(:signature, :invalid_date)
|
||||
end
|
||||
|
||||
timestamp_signed_digest = ASN1::Timestamp.signed_digest(self.signature.download)
|
||||
if timestamp_signed_digest != self.digest
|
||||
errors.add(:signature)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -12,6 +12,7 @@ class DossierOperationLog < ApplicationRecord
|
|||
|
||||
belongs_to :dossier
|
||||
has_one_attached :serialized
|
||||
belongs_to :bill_signature, optional: true
|
||||
|
||||
def self.create_and_serialize(params)
|
||||
dossier = params.fetch(:dossier)
|
||||
|
|
27
config/locales/models/bill_signature/fr.yml
Normal file
27
config/locales/models/bill_signature/fr.yml
Normal file
|
@ -0,0 +1,27 @@
|
|||
fr:
|
||||
activerecord:
|
||||
attributes:
|
||||
bill_signature:
|
||||
dossier_operation_logs:
|
||||
one: opération
|
||||
other: opérations
|
||||
digest: empreinte
|
||||
serialized: liasse
|
||||
signature: signature
|
||||
errors:
|
||||
models:
|
||||
bill_signature:
|
||||
attributes:
|
||||
digest:
|
||||
invalid: 'ne correspond pas à la liasse'
|
||||
serialized:
|
||||
blank: 'doit être rempli'
|
||||
invalid: 'ne correspond pas aux opérations'
|
||||
signature:
|
||||
blank: 'doit être rempli'
|
||||
invalid: 'ne correspond pas à l’empreinte'
|
||||
invalid_date: 'ne doit pas être dans le futur'
|
||||
models:
|
||||
bill_signature:
|
||||
one: Horodatage
|
||||
other: Horodatages
|
10
db/migrate/20190616141702_create_bill_signature.rb
Normal file
10
db/migrate/20190616141702_create_bill_signature.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
class CreateBillSignature < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :bill_signatures do |t|
|
||||
t.string :digest
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_reference :dossier_operation_logs, :bill_signature, foreign_key: true
|
||||
end
|
||||
end
|
11
db/schema.rb
11
db/schema.rb
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2019_06_07_124156) do
|
||||
ActiveRecord::Schema.define(version: 2019_06_16_141702) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -145,6 +145,12 @@ ActiveRecord::Schema.define(version: 2019_06_07_124156) do
|
|||
t.index ["gestionnaire_id"], name: "index_avis_on_gestionnaire_id"
|
||||
end
|
||||
|
||||
create_table "bill_signatures", force: :cascade do |t|
|
||||
t.string "digest"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
create_table "champs", id: :serial, force: :cascade do |t|
|
||||
t.string "value"
|
||||
t.integer "type_de_champ_id"
|
||||
|
@ -224,7 +230,9 @@ ActiveRecord::Schema.define(version: 2019_06_07_124156) do
|
|||
t.datetime "keep_until"
|
||||
t.datetime "executed_at"
|
||||
t.text "digest"
|
||||
t.bigint "bill_signature_id"
|
||||
t.index ["administration_id"], name: "index_dossier_operation_logs_on_administration_id"
|
||||
t.index ["bill_signature_id"], name: "index_dossier_operation_logs_on_bill_signature_id"
|
||||
t.index ["dossier_id"], name: "index_dossier_operation_logs_on_dossier_id"
|
||||
t.index ["gestionnaire_id"], name: "index_dossier_operation_logs_on_gestionnaire_id"
|
||||
t.index ["keep_until"], name: "index_dossier_operation_logs_on_keep_until"
|
||||
|
@ -617,6 +625,7 @@ ActiveRecord::Schema.define(version: 2019_06_07_124156) do
|
|||
add_foreign_key "closed_mails", "procedures"
|
||||
add_foreign_key "commentaires", "dossiers"
|
||||
add_foreign_key "dossier_operation_logs", "administrations"
|
||||
add_foreign_key "dossier_operation_logs", "bill_signatures"
|
||||
add_foreign_key "dossier_operation_logs", "dossiers"
|
||||
add_foreign_key "dossier_operation_logs", "gestionnaires"
|
||||
add_foreign_key "dossiers", "users"
|
||||
|
|
6
spec/factories/bill_signature.rb
Normal file
6
spec/factories/bill_signature.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
FactoryBot.define do
|
||||
factory :bill_signature do
|
||||
serialized { Rack::Test::UploadedFile.new("./spec/fixtures/files/bill_signature/serialized.json", 'application/json') }
|
||||
signature { Rack::Test::UploadedFile.new("./spec/fixtures/files/bill_signature/signature.der", 'application/x-x509-ca-cert') }
|
||||
end
|
||||
end
|
5
spec/factories/dossier_operation_log.rb
Normal file
5
spec/factories/dossier_operation_log.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
FactoryBot.define do
|
||||
factory :dossier_operation_log do
|
||||
operation { :passer_en_instruction }
|
||||
end
|
||||
end
|
1
spec/fixtures/files/bill_signature/serialized.json
vendored
Normal file
1
spec/fixtures/files/bill_signature/serialized.json
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"dossier1": "hash1", "dossier2": "hash2"}
|
158
spec/models/bill_signature_spec.rb
Normal file
158
spec/models/bill_signature_spec.rb
Normal file
|
@ -0,0 +1,158 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe BillSignature, type: :model do
|
||||
describe 'validations' do
|
||||
describe 'check_bill_digest' do
|
||||
before do
|
||||
subject.dossier_operation_logs = dossier_operation_logs
|
||||
subject.digest = digest
|
||||
subject.valid?
|
||||
end
|
||||
|
||||
context 'no operations' do
|
||||
let(:dossier_operation_logs) { [] }
|
||||
|
||||
context 'correct digest' do
|
||||
let(:digest) { Digest::SHA256.hexdigest('{}') }
|
||||
|
||||
it { expect(subject.errors.details[:digest]).to be_empty }
|
||||
end
|
||||
|
||||
context 'bad digest' do
|
||||
let(:digest) { 'baadf00d' }
|
||||
|
||||
it { expect(subject.errors.details[:digest]).to eq [error: :invalid] }
|
||||
end
|
||||
end
|
||||
|
||||
context 'operations set, good digest' do
|
||||
let(:dossier_operation_logs) { [build(:dossier_operation_log, id: '1234', digest: 'abcd')] }
|
||||
|
||||
context 'correct digest' do
|
||||
let(:digest) { Digest::SHA256.hexdigest('{"1234":"abcd"}') }
|
||||
|
||||
it { expect(subject.errors.details[:digest]).to be_empty }
|
||||
end
|
||||
|
||||
context 'bad digest' do
|
||||
let(:digest) { 'baadf00d' }
|
||||
|
||||
it { expect(subject.errors.details[:digest]).to eq [error: :invalid] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'check_serialized_bill_contents' do
|
||||
before do
|
||||
subject.dossier_operation_logs = dossier_operation_logs
|
||||
subject.serialized.attach(io: StringIO.new(serialized), filename: 'file') if serialized.present?
|
||||
subject.valid?
|
||||
end
|
||||
|
||||
context 'no operations' do
|
||||
let(:dossier_operation_logs) { [] }
|
||||
let(:serialized) { '{}' }
|
||||
|
||||
it { expect(subject.errors.details[:serialized]).to be_empty }
|
||||
end
|
||||
|
||||
context 'operations set' do
|
||||
let(:dossier_operation_logs) { [build(:dossier_operation_log, id: '1234', digest: 'abcd')] }
|
||||
let(:serialized) { '{"1234":"abcd"}' }
|
||||
|
||||
it { expect(subject.errors.details[:serialized]).to be_empty }
|
||||
end
|
||||
|
||||
context 'serialized not set' do
|
||||
let(:dossier_operation_logs) { [] }
|
||||
let(:serialized) { nil }
|
||||
|
||||
it { expect(subject.errors.details[:serialized]).to eq [error: :blank] }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'check_signature_contents' do
|
||||
before do
|
||||
subject.signature.attach(io: StringIO.new(signature), filename: 'file') if signature.present?
|
||||
allow(ASN1::Timestamp).to receive(:signature_time).and_return(signature_time)
|
||||
allow(ASN1::Timestamp).to receive(:signed_digest).and_return(signed_digest)
|
||||
subject.digest = digest
|
||||
subject.valid?
|
||||
end
|
||||
|
||||
context 'correct signature' do
|
||||
let(:signature) { 'signature' }
|
||||
let(:signature_time) { 1.day.ago }
|
||||
let(:digest) { 'abcd' }
|
||||
let(:signed_digest) { 'abcd' }
|
||||
|
||||
it { expect(subject.errors.details[:signature]).to be_empty }
|
||||
end
|
||||
|
||||
context 'signature not set' do
|
||||
let(:signature) { nil }
|
||||
let(:signature_time) { 1.day.ago }
|
||||
let(:digest) { 'abcd' }
|
||||
let(:signed_digest) { 'abcd' }
|
||||
|
||||
it { expect(subject.errors.details[:signature]).to eq [error: :blank] }
|
||||
end
|
||||
|
||||
context 'wrong signature time' do
|
||||
let(:signature) { 'signature' }
|
||||
let(:signature_time) { 1.day.from_now }
|
||||
let(:digest) { 'abcd' }
|
||||
let(:signed_digest) { 'abcd' }
|
||||
|
||||
it { expect(subject.errors.details[:signature]).to eq [error: :invalid_date] }
|
||||
end
|
||||
|
||||
context 'wrong signature digest' do
|
||||
let(:signature) { 'signature' }
|
||||
let(:signature_time) { 1.day.ago }
|
||||
let(:digest) { 'abcd' }
|
||||
let(:signed_digest) { 'dcba' }
|
||||
|
||||
it { expect(subject.errors.details[:signature]).to eq [error: :invalid] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.build_with_operations' do
|
||||
subject { described_class.build_with_operations(dossier_operation_logs, Date.new(1871, 03, 18)) }
|
||||
|
||||
context 'no operations' do
|
||||
let(:dossier_operation_logs) { [] }
|
||||
|
||||
it { expect(subject.operations_bill).to eq({}) }
|
||||
it { expect(subject.digest).to eq(Digest::SHA256.hexdigest('{}')) }
|
||||
it { expect(subject.serialized.download).to eq('{}') }
|
||||
it { expect(subject.serialized.filename).to eq('demarches-simplifiees-operations-1871-03-18.json') }
|
||||
end
|
||||
|
||||
context 'one operation' do
|
||||
let(:dossier_operation_logs) do
|
||||
[build(:dossier_operation_log, id: '1234', digest: 'abcd')]
|
||||
end
|
||||
|
||||
it { expect(subject.operations_bill).to eq({ '1234' => 'abcd' }) }
|
||||
it { expect(subject.digest).to eq(Digest::SHA256.hexdigest('{"1234":"abcd"}')) }
|
||||
it { expect(subject.serialized.download).to eq('{"1234":"abcd"}') }
|
||||
it { expect(subject.serialized.filename).to eq('demarches-simplifiees-operations-1871-03-18.json') }
|
||||
end
|
||||
|
||||
context 'several operations' do
|
||||
let(:dossier_operation_logs) do
|
||||
[
|
||||
build(:dossier_operation_log, id: '1234', digest: 'abcd'),
|
||||
build(:dossier_operation_log, id: '5678', digest: 'dcba')
|
||||
]
|
||||
end
|
||||
|
||||
it { expect(subject.operations_bill).to eq({ '1234' => 'abcd', '5678' => 'dcba' }) }
|
||||
it { expect(subject.digest).to eq(Digest::SHA256.hexdigest('{"1234":"abcd","5678":"dcba"}')) }
|
||||
it { expect(subject.serialized.download).to eq('{"1234":"abcd","5678":"dcba"}') }
|
||||
it { expect(subject.serialized.filename).to eq('demarches-simplifiees-operations-1871-03-18.json') }
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue