Generating PDF's with signatures using Ruby or Ruby on Rails - ruby-on-rails

I'm working on a Rails application that uses prawn to generate PDF's. Long story short, we want to be able to digitally sign generated PDF's. Im not sure where to start reading up exactly. Just wanted to ask if anybody else has been able to accomplish this before, and if so, what kind of resources I should be using to do it.
Thanks!

Though this question has answer and quite old, I would like to add relevant link for information sake.
MrWater has shared his code to Insert digital signature into existing pdf file.
VERSION 1 - Generate certificate and key file, and insert them directly into the document
require 'openssl'
begin
require 'origami'
rescue LoadError
ORIGAMIDIR = "C:\RailsInstaller\Ruby1.9.3\lib\ruby\gems\1.9.1\gems\origami-1.2.4\lib"
$: << ORIGAMIDIR
require 'origami'
end
include Origami
# Code below is based on documentation available on
# http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL.html
key = OpenSSL::PKey::RSA.new 2048
open 'private_key.pem', 'w' do |io| io.write key.to_pem end
open 'public_key.pem', 'w' do |io| io.write key.public_key.to_pem end
cipher = OpenSSL::Cipher::Cipher.new 'AES-128-CBC'
pass_phrase = 'Origami rocks'
key_secure = key.export cipher, pass_phrase
open 'private_key.pem', 'w' do |io|
io.write key_secure
end
#Create the certificate
name = OpenSSL::X509::Name.parse 'CN=nobody/DC=example'
cert = OpenSSL::X509::Certificate.new
cert.version = 2
cert.serial = 0
cert.not_before = Time.now
cert.not_after = Time.now + 3600
cert.public_key = key.public_key
cert.subject = name
OUTPUTFILE = "test.pdf"
contents = ContentStream.new.setFilter(:FlateDecode)
contents.write OUTPUTFILE,
:x => 350, :y => 750, :rendering => Text::Rendering::STROKE, :size => 30
pdf = PDF.read('Sample.pdf')
# Open certificate files
#sigannot = Annotation::Widget::Signature.new
#sigannot.Rect = Rectangle[:llx => 89.0, :lly => 386.0, :urx => 190.0, :ury => 353.0]
#page.add_annot(sigannot)
# Sign the PDF with the specified keys
pdf.sign(cert, key,
:method => 'adbe.pkcs7.sha1',
#:annotation => sigannot,
:location => "Portugal",
:contact => "myemail#email.tt",
:reason => "Proof of Concept"
)
# Save the resulting file
pdf.save(OUTPUTFILE)
VERSION 2 - Use existing certificates to sign a pdf document
require 'openssl'
begin
require 'origami'
rescue LoadError
ORIGAMIDIR = "C:\RailsInstaller\Ruby1.9.3\lib\ruby\gems\1.9.1\gems\origami-1.2.4\lib"
$: << ORIGAMIDIR
require 'origami'
end
include Origami
INPUTFILE = "Sample.pdf"
#inputfile = String.new(INPUTFILE)
OUTPUTFILE = #inputfile.insert(INPUTFILE.rindex("."),"_signed")
CERTFILE = "certificate.pem"
RSAKEYFILE = "private_key.pem"
passphrase = "your passphrase"
key4pem=File.read RSAKEYFILE
key = OpenSSL::PKey::RSA.new key4pem, passphrase
cert = OpenSSL::X509::Certificate.new(File.read CERTFILE)
pdf = PDF.read(INPUTFILE)
page = pdf.get_page(1)
# Add signature annotation (so it becomes visibles in pdf document)
sigannot = Annotation::Widget::Signature.new
sigannot.Rect = Rectangle[:llx => 89.0, :lly => 386.0, :urx => 190.0, :ury => 353.0]
page.add_annot(sigannot)
# Sign the PDF with the specified keys
pdf.sign(cert, key,
:method => 'adbe.pkcs7.sha1',
:annotation => sigannot,
:location => "Portugal",
:contact => "myemail#email.tt",
:reason => "Proof of Concept"
)
# Save the resulting file
pdf.save(OUTPUTFILE)

I'd suggest you to take a look at https://github.com/joseicosta/odf-report.
It's a gem for generating ODF, but they can easily be converted to PDF, plus it supports templates.

Related

Format encrypted string to pass as URL param

I am using the 'encryptor' gem to scramble a string that I want to include as a URL parameter on a redirect. Unfortunately, the URI::encode function does not turn the encrypted string into an acceptable format to be included into the URL. How can I turn it into something that can be passed as a URL parameter?
salt = Time.now.to_i.to_s
secret_key = 'secret'
iv = OpenSSL::Cipher::Cipher.new('rc2').random_iv
encrypted_url = Encryptor.encrypt("some url parameter as string", :algorithm => 'rc2', :key => secret_key, :iv => iv, :salt => salt)
url = URI::encode(encrypted_url)
redirect_to 'http://domain.com/' + url
Base64 is recommended in this use case.
Addressable
Genereally encoding URL's should be done with the Addressable gem. In your case you're using non UTF-8 characters which will raise an error with the standard parse. So you'll need to use the encode feature of addressable.
require 'encryptor'
require 'openssl'
require 'addressable/uri'
salt = Time.now.to_i.to_s
# => "1414221973"
secret_key = 'secret'
# => "secret"
iv = OpenSSL::Cipher::Cipher.new('rc2').random_iv
# => "\x97\xE5\x83\xFF#\x97\x0Fn"
encrypted_url = Encryptor.encrypt("some url parameter as string", :algorithm => 'rc2', :key => secret_key, :iv => iv, :salt => salt)
# => "\xD6\x1D\x1A\x8A\x06f\x91\x91I\xD2\x04\xEB\x81\xFF\xCC&\xFA\e\x94,\xAE\xA0\xDA\xFA\xD2\xD8w\xF3\xD4\x8E\xB64"
url = Addressable::URI.encode_component(encrypted_url)
# => "%D6%1D%1A%8A%06f%91%91I%D2%04%EB%81%FF%CC&%FA%1B%94,%AE%A0%DA%FA%D2%D8w%F3%D4%8E%B64"
redirect_to 'http://domain.com/?' + url # You'll want to prepend url params with a question mark
For the URL I recommend give the encrypted string a param name
'http://domain.com/?encsite=' + url
NOTE: I'm uncertain as to whether these % symbols are permitted as is in URLs. You may need to URI.encode the result to exchange % to %25.
In my tests with Addressable I got the following:
require 'addressable/uri'
#...
url = Addressable::URI.parse(encrypted_url)
# => #<Addressable::URI:0x93bcf0 URI:��f��I�����&�,������w�Ԏ�4>
url.normalize
# ArgumentError: invalid byte sequence in UTF-8
# SO ENCODE INSTEAD
url = Addressable::URI.encode_component(encrypted_url)
# => "%D6%1D%1A%8A%06f%91%91I%D2%04%EB%81%FF%CC&%FA%1B%94,%AE%A0%DA%FA%D2%D8w%F3%D4%8E%B64"
For more in depth Addressable encoding information you can find the list of methods with description here: http://www.rubydoc.info/gems/addressable/Addressable/URI
Base64
You can just use Base64 instead. E.G.)
require 'base64'
#...
url = Base64.encode64(encrypted_url)
# => "1h0aigZmkZFJ0gTrgf/MJvoblCyuoNr60th389SOtjQ=\n"
url.chomp!
# => "1h0aigZmkZFJ0gTrgf/MJvoblCyuoNr60th389SOtjQ="
Base64.decode64(url) == encrypted_url
# => true

Reading and writing file attributes

In the rails console:
ActionDispatch::Http::UploadedFile.new tempfile: 'tempfilefoo', original_filename: 'filename_foo.jpg', content_type: 'content_type_foo', headers: 'headers_foo'
=> #<ActionDispatch::Http::UploadedFile:0x0000000548f3a0 #tempfile="tempfilefoo", #original_filename=nil, #content_type=nil, #headers=nil>
I can write a string to #tempfile, and yet #original_filename, #content_type and #headers remain as nil
Why is this and how can I write information to these attributes?
And how can I read these attributes from a file instance?
i.e.
File.new('path/to/file.png')
It's not documented (and doesn't make much sense), but it looks like the options UploadedFile#initialize takes are :tempfile, :filename, :type and :head:
def initialize(hash) # :nodoc:
#tempfile = hash[:tempfile]
raise(ArgumentError, ':tempfile is required') unless #tempfile
#original_filename = encode_filename(hash[:filename])
#content_type = hash[:type]
#headers = hash[:head]
end
Changing your invocation to this ought to work:
ActionDispatch::Http::UploadedFile.new tempfile: 'tempfilefoo',
filename: 'filename_foo.jpg', type: 'content_type_foo', head: 'headers_foo'
Or you can set them after initialization:
file = ActionDispatch::Http::UploadedFile.new tempfile: 'tempfilefoo', filename: 'filename_foo.jpg'
file.content_type = 'content_type_foo'
file.headers = 'headers_foo'
I'm not sure I understand your second question, "And how can I read these attributes from a file instance?"
You can extract the filename (or last component) from any path with File.basename:
file = File.new('path/to/file.png')
File.basename(file.path) # => "file.png"
If you want to get the Content-Type that corresponds to a file extension, you can use Rails' Mime module:
type = Mime["png"] # => #<Mime::Type:... #synonyms=[], #symbol=:png, #string="text/png">
type.to_s # => "text/png"
You can put this together with File.extname, which gives you the extension:
ext = File.extname("path/to/file.png") # => ".png"
ext = ext.sub(/^\./, '') # => "png" (drop the leading dot)
Mime[ext].to_s # => "text/png"
You can see a list of all of the MIME types Rails knows about by typing Mime::SET in the Rails console, or looking at the source, which also shows you how to register other MIME types in case you're expecting other types of files.
the following should help you:
upload = ActionDispatch::Http::UploadedFile.new({
:tempfile => File.new("#{Rails.root}/relative_path/to/tempfilefoo") , #make sure this file exists
:filename => "filename_foo" # use this instead of original_filename
})
upload.headers = "headers_foo"
upload.content_type = "content_type_foo"
I didn't understand by "And how can I read these attributes from a file instance?", what you exactly want to do.
Perhaps if you want to read the tempfile, you can use:
upload.read # -> content of tempfile
upload.rewind # -> rewinds the pointer back so that you can read it again.
Hope it helps :) And let me know if I have misunderstood.

Invalid encoding with rqrcode

I'm having an invalid encoding error that doesn't let me save the image to a carrierwave uploader.
require 'rqrcode_png'
img = RQRCode::QRCode.new( 'test', :size => 4, :level => :h ).to_img.to_s
img.valid_encoding?
=> false
I'm not sure if this is what you're looking for, in my case I needed to associate the generated QR code with a Rails model using carrierwave, what I ended up doing was saving the image to a temp file, associating that file with the model and afterwards deleting the temp file, here's my code:
def generate_qr_code!
tmp_path = Rails.root.join('tmp', "some-filename.png")
tmp_file = RQRCode::QRCode.new(self.hash_value).to_img.resize(200,200).save(tmp_path)
# Stream is handed closed, we need to reopen it
File.open(tmp_file.path) do |file|
self.qr_code = file
end
File.delete(tmp_file.path)
self.save!
end

Storing image using open URI and paperclip having size less than 10kb

I want to import some icons from my old site. The size of those icons is less than 10kb. So when I am trying to import the icons its returning stringio.txt file.
require "open-uri"
class Category < ActiveRecord::Base
has_attached_file :icon, :path => ":rails_root/public/:attachment/:id/:style/:basename.:extension"
def icon_from_url(url)
self.icon = open(url)
end
end
In rake task.
category = Category.new
category.icon_from_url "https://xyz.com/images/dog.png"
category.save
Try:
def icon_from_url(url)
extname = File.extname(url)
basename = File.basename(url, extname)
file = Tempfile.new([basename, extname])
file.binmode
open(URI.parse(url)) do |data|
file.write data.read
end
file.rewind
self.icon = file
end
To override the default filename of a "fake file upload" in Paperclip (stringio.txt on small files or an almost random temporary name on larger files) you have 2 main possibilities:
Define an original_filename on the IO:
def icon_from_url(url)
io = open(url)
io.original_filename = "foo.png"
self.icon = io
end
You can also get the filename from the URI:
io.original_filename = File.basename(URI.parse(url).path)
Or replace :basename in your :path:
has_attached_file :icon, :path => ":rails_root/public/:attachment/:id/:style/foo.png", :url => "/:attachment/:id/:style/foo.png"
Remember to alway change the :url when you change the :path, otherwise the icon.url method will be wrong.
You can also define you own custom interpolations (e.g. :rails_root/public/:whatever).
You are almost there I think, try opening parsed uri, not the string.
require "open-uri"
class Category < ActiveRecord::Base
has_attached_file :icon, :path =>:rails_root/public/:attachment/:id/:style/:basename.:extension"
def icon_from_url(url)
self.icon = open(URI.parse(url))
end
end
Of course this doesn't handle errors
You can also disable OpenURI from ever creating a StringIO object, and force it to create a temp file instead. See this SO answer:
Why does Ruby open-uri's open return a StringIO in my unit test, but a FileIO in my controller?
In the past, I found the most reliable way to retrieve remote files was by using the command line tool "wget". The following code is mostly copied straight from an existing production (Rails 2.x) app with a few tweaks to fit with your code examples:
class CategoryIconImporter
def self.download_to_tempfile (url)
system(wget_download_command_for(url))
##tempfile.path
end
def self.clear_tempfile
##tempfile.delete if ##tempfile && ##tempfile.path && File.exist?(##tempfile.path)
##tempfile = nil
end
def self.set_wget
# used for retrieval in NrlImage (and in future from other sies?)
if !##wget
stdin, stdout, stderr = Open3.popen3('which wget')
##wget = stdout.gets
##wget ||= '/usr/local/bin/wget'
##wget.strip!
end
end
def self.wget_download_command_for (url)
set_wget
##tempfile = Tempfile.new url.sub(/\?.+$/, '').split(/[\/\\]/).last
command = [ ##wget ]
command << '-q'
if url =~ /^https/
command << '--secure-protocol=auto'
command << '--no-check-certificate'
end
command << '-O'
command << ##tempfile.path
command << url
command.join(' ')
end
def self.import_from_url (category_params, url)
clear_tempfile
filename = url.sub(/\?.+$/, '').split(/[\/\\]/).last
found = MIME::Types.type_for(filename)
content_type = !found.empty? ? found.first.content_type : nil
download_to_tempfile url
nicer_path = RAILS_ROOT + '/tmp/' + filename
File.copy ##tempfile.path, nicer_path
Category.create(category_params.merge({:icon => ActionController::TestUploadedFile.new(nicer_path, content_type, true)}))
end
end
The rake task logic might look like:
[
['Cat', 'cat'],
['Dog', 'dog'],
].each do |name, icon|
CategoryIconImporter.import_from_url {:name => name}, "https://xyz.com/images/#{icon}.png"
end
This uses the mime-types gem for content type discovery:
gem 'mime-types', :require => 'mime/types'

MIgrating data from EC2 to S3?

I am running a Rails app using Paperclip to take care of file attachments and image resizing, etc. The app is currently hosted on EngineYard cloud, and all attachments are stored in their EBS. Thinking about using S3 to handle all Paperclip attachments.
Does anyone know of a good and safe way for this migration? many thanks!
You could work up a rake task that iterates over your attachments and pushes each to S3. I used this one awhile back with attachment_fu -- wouldn't be too different. This uses the aws-s3 gem.
Basically the process is:
1. Select files from the database that need to be moved
2. Push them to S3
3. Update database to reflect that the file is no longer stored locally (this way you can do them in batches and don't need to worry about pushing the same file twice).
#attachments = Attachment.stored_locally
#attachments.each do |attachment|
base_path = RAILS_ROOT + '/public/assets/'
attachment_folder = ((attachment.respond_to?(:parent_id) && attachment.parent_id) || attachment.id).to_s
full_filename = File.join(base_path, ("%08d" % attachment_folder).scan(/..../), attachment.filename)
require 'aws/s3'
AWS::S3::Base.establish_connection!(
:access_key_id => S3_CONFIG[:access_key_id],
:secret_access_key => S3_CONFIG[:secret_access_key]
)
AWS::S3::S3Object.store(
'assets/' + attachment_folder + '/' + attachment.filename,
File.open(full_filename),
S3_CONFIG[:bucket_name],
:content_type => attachment.content_type,
:access => :private
)
if AWS::S3::Service.response.success?
# Update the database
attachment.update_attribute(:stored_on_s3, true)
# Remove the file on the local filesystem
FileUtils.rm full_filename
# Remove directory also if it is now empty
Dir.rmdir(File.dirname(full_filename)) if (Dir.entries(File.dirname(full_filename))-['.','..']).empty?
else
puts "There was a problem uploading " + full_filename
end
end
I found myself in the same situation and took bensie's code and made it work for myself - this is what I came up with:
require 'aws/s3'
# Ensure you do the following:
# export AMAZON_ACCESS_KEY_ID='your-access-key'
# export AMAZON_SECRET_ACCESS_KEY='your-secret-word-thingy'
AWS::S3::Base.establish_connection!
#failed = []
#attachments = Asset.all # Asset paperclip attachment is: has_attached_file :attachment....
#attachments.each do |asset|
begin
puts "Processing #{asset.id}"
base_path = RAILS_ROOT + '/public/'
attachment_folder = ((asset.respond_to?(:parent_id) && asset.parent_id) || asset.id).to_s
styles = asset.attachment.styles.keys
styles << :original
styles.each do |style|
full_filename = File.join(base_path, asset.attachment.url(style, false))
AWS::S3::S3Object.store(
'attachments/' + attachment_folder + '/' + style.to_s + "/" + asset.attachment_file_name,
File.open(full_filename),
"swellnet-assets",
:content_type => asset.attachment_content_type,
:access => (style == :original ? :private : :public_read)
)
if AWS::S3::Service.response.success?
puts "Stored #{asset.id}[#{style.to_s}] on S3..."
else
puts "There was a problem uploading " + full_filename
end
end
rescue
puts "Error with #{asset.id}"
#failed << asset.id
end
end
puts "Failed uploads: #{#failed.join(", ")}" unless #failed.empty?
Of course, if you have multiple models you will need to adjust as necessary...

Resources