add pdf generation

This commit is contained in:
Alex Turchyn
2023-05-28 23:20:48 +03:00
parent 170cb1ecea
commit 6a41f82e5b
21 changed files with 383 additions and 49 deletions
+90
View File
@@ -0,0 +1,90 @@
# frozen_string_literal: true
module GenerateCertificate
NAME = 'DocuSeal'
SIZE = 2**11
module_function
def call(name = NAME)
root_cert, root_key = generate_root_ca(name)
sub_cert, sub_key = generate_sub_ca(name, root_cert, root_key)
cert, key = generate_certificate(name, sub_cert, sub_key)
{
cert: cert.to_pem,
key: key.to_pem,
root_ca: root_cert.to_pem,
root_key: root_key.to_pem,
sub_ca: sub_cert.to_pem,
sub_key: sub_key.to_pem
}
end
def generate_root_ca(name)
key = OpenSSL::PKey::RSA.new(SIZE)
cert = OpenSSL::X509::Certificate.new
cert.subject = OpenSSL::X509::Name.parse("/C=AT/O=#{name}/CN=#{name} Root CA")
cert.issuer = cert.subject
cert.not_before = Time.current
cert.not_after = 100.years.from_now
cert.public_key = key.public_key
cert.serial = OpenSSL::BN.rand(160)
ef = OpenSSL::X509::ExtensionFactory.new
ef.subject_certificate = cert
ef.issuer_certificate = cert
cert.add_extension(ef.create_extension('basicConstraints', 'CA:TRUE', true))
cert.add_extension(ef.create_extension('keyUsage', 'Certificate Sign, CRL Sign', true))
cert.add_extension(ef.create_extension('subjectKeyIdentifier', 'hash', false))
cert.sign(key, OpenSSL::Digest.new('SHA256'))
[cert, key]
end
def generate_sub_ca(name, root_ca_cert, root_ca_key)
key = OpenSSL::PKey::RSA.new(SIZE)
cert = OpenSSL::X509::Certificate.new
cert.subject = OpenSSL::X509::Name.parse("/C=AT/O=#{name}/CN=#{name} Sub-CA")
cert.issuer = root_ca_cert.subject
cert.not_before = Time.current
cert.not_after = 100.years.from_now
cert.public_key = key.public_key
cert.serial = OpenSSL::BN.rand(160)
ef = OpenSSL::X509::ExtensionFactory.new
ef.subject_certificate = cert
ef.issuer_certificate = root_ca_cert
cert.add_extension(ef.create_extension('basicConstraints', 'CA:TRUE', true))
cert.add_extension(ef.create_extension('keyUsage', 'Certificate Sign, CRL Sign', true))
cert.add_extension(ef.create_extension('subjectKeyIdentifier', 'hash', false))
cert.sign(root_ca_key, OpenSSL::Digest.new('SHA256'))
[cert, key]
end
def generate_certificate(name, ca_cert, ca_key)
key = OpenSSL::PKey::RSA.new(SIZE)
cert = OpenSSL::X509::Certificate.new
cert.subject = OpenSSL::X509::Name.parse("/C=AT/O=#{name}/CN=#{name} Certificate")
cert.issuer = ca_cert.subject
cert.not_before = Time.current
cert.not_after = 100.years.from_now
cert.public_key = key.public_key
cert.serial = OpenSSL::BN.rand(160)
ef = OpenSSL::X509::ExtensionFactory.new
ef.subject_certificate = cert
ef.issuer_certificate = ca_cert
cert.add_extension(ef.create_extension('basicConstraints', 'CA:FALSE', true))
cert.add_extension(ef.create_extension('keyUsage', 'Digital Signature', true))
cert.add_extension(ef.create_extension('subjectKeyIdentifier', 'hash', false))
cert.sign(ca_key, OpenSSL::Digest.new('SHA256'))
[cert, key]
end
end
+15
View File
@@ -0,0 +1,15 @@
# frozen_string_literal: true
module PdfIcons
PATH = Rails.root.join('lib/pdf_icons')
module_function
def check_io
@check_io ||= StringIO.new(PATH.join('check.png').read)
end
def paperclip_io
@paperclip_io ||= StringIO.new(PATH.join('paperclip.png').read)
end
end
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

+107 -36
View File
@@ -2,53 +2,124 @@
module Submissions
module GenerateResultAttachments
FONT_SIZE = 12
FONT_NAME = 'Helvetica'
module_function
# rubocop:disable Metrics
def call(submission)
cert = submission.flow.account.encrypted_configs
.find_by(key: EncryptedConfig::ESIGN_CERTS_KEY).value
zip_file = Tempfile.new
zip_stream = Zip::ZipOutputStream.open(zip_file)
submission.flow.schema.map do |item|
document = submission.flow.documents.find { |e| e.uuid == item['attachment_uuid'] }
field_area_index = Flows.build_field_areas_index(submission.flow)
document.open do |tempfile|
pdf = CombinePDF.load(tempfile.path)
pdf.pages.each_with_index do |page, index|
blocks = field_area_index.dig(document.uuid, index)
next if blocks.blank?
blocks.each do |block|
area, field = block.values_at(:area, :field)
page.textbox(submission.values[field['uuid']],
x: area['x'] * page.page_size[2],
y: page.page_size[3] - (area['y'] * page.page_size[3]),
width: area['w'] * page.page_size[2],
height: area['h'] * page.page_size[3])
end
end
string = pdf.to_pdf
io = StringIO.new(string)
zip_stream.put_next_entry("#{item['name']}.pdf")
zip_stream.write(string)
ActiveStorage::Attachment.create!(
blob: ActiveStorage::Blob.create_and_upload!(
io:, filename: "#{item['name']}.pdf"
),
name: 'documents',
record: submission
)
pdfs_index =
submission.flow.documents.to_h do |attachment|
[attachment.uuid, HexaPDF::Document.new(io: StringIO.new(attachment.download))]
end
submission.flow.fields.each do |field|
field.fetch('areas', []).each do |area|
pdf = pdfs_index[area['attachment_uuid']]
page = pdf.pages[area['page']]
width = page.box.width
height = page.box.height
value = submission.values[field['uuid']]
canvas = page.canvas(type: :overlay)
case field['type']
when 'image', 'signature'
attachment = submission.attachments.find { |a| a.uuid == value }
io = StringIO.new(attachment.download)
Vips::Image.new_from_buffer(io.read, '')
scale = [(area['w'] * width) / attachment.metadata['width'],
(area['h'] * height) / attachment.metadata['height']].min
canvas.image(io, at: [area['x'] * width,
height - (area['y'] * height) -
(((attachment.metadata['height'] * scale) + (area['h'] * height)) / 2)],
width: attachment.metadata['width'] * scale,
height: attachment.metadata['height'] * scale)
when 'attachment'
Array.wrap(value).each_with_index do |uuid, index|
attachment = submission.attachments.find { |a| a.uuid == uuid }
canvas.image(PdfIcons.paperclip_io,
at: [area['x'] * width,
height - ((area['y'] * height) + (1.2 * FONT_SIZE) - (FONT_SIZE * index))],
width: FONT_SIZE, height: FONT_SIZE)
canvas.font(FONT_NAME, size: FONT_SIZE)
canvas.text(attachment.filename.to_s,
at: [(area['x'] * width) + FONT_SIZE,
height - ((area['y'] * height) + FONT_SIZE - (FONT_SIZE * index))])
page[:Annots] ||= []
page[:Annots] << pdf.add({
Type: :Annot, Subtype: :Link,
Rect: [
area['x'] * width,
height - (area['y'] * height),
(area['x'] * width) + (area['w'] * width),
height - (area['y'] * height) - FONT_SIZE
],
A: { Type: :Action, S: :URI, URI: attachment.url }
})
end
when 'checkbox'
Array.wrap(value).each_with_index do |value, index|
canvas.image(PdfIcons.check_io,
at: [area['x'] * width,
height - ((area['y'] * height) + (1.2 * FONT_SIZE) - (FONT_SIZE * index))],
width: FONT_SIZE, height: FONT_SIZE)
canvas.font(FONT_NAME, size: FONT_SIZE)
canvas.text(value,
at: [(area['x'] * width) + FONT_SIZE,
height - ((area['y'] * height) + FONT_SIZE - (FONT_SIZE * index))])
end
when 'date'
canvas.font(FONT_NAME, size: FONT_SIZE)
canvas.text(I18n.l(Date.parse(value)), at: [area['x'] * width, height - ((area['y'] * height) + FONT_SIZE)])
else
canvas.font(FONT_NAME, size: FONT_SIZE)
canvas.text(value.to_s, at: [area['x'] * width, height - ((area['y'] * height) + FONT_SIZE)])
end
end
end
submission.flow.schema.map do |item|
document = submission.flow.documents.find { |a| a.uuid == item['attachment_uuid'] }
io = StringIO.new
zip_stream.put_next_entry("#{item['name']}.pdf")
zip_stream.write(io.string)
pdf = pdfs_index[item['attachment_uuid']]
pdf.sign(io, reason: "Signed by #{submission.email}",
doc_mdp_permissions: :no_changes,
certificate: OpenSSL::X509::Certificate.new(cert['cert']),
key: OpenSSL::PKey::RSA.new(cert['key']),
certificate_chain: [OpenSSL::X509::Certificate.new(cert['sub_ca']),
OpenSSL::X509::Certificate.new(cert['root_ca'])])
submission.documents.attach(io: StringIO.new(io.string), filename: document.filename)
end
zip_stream.close
submission.archive.attach(io: zip_file, filename: 'submission.zip')
submission.archive.attach(io: zip_file, filename: "#{submission.flow.name}.zip")
end
# rubocop:enable Metrics
end
end