How do you use fixtures with attr_encrypted - ruby-on-rails

I want to test a model that uses attr_encrypted to encrypt a secret in the database
class Thing
attr_encrypted :secret, encode: true
end
But when I define the secret in a fixture the encoded newline character gets escaped out.
one:
encrypted_secret: '<%= Thing.encrypt_secret(SecureRandom.uuid) %>'
That is:
'axZFZEknxUSYdUlPhwLBbj8CwSeCW5at2INA98EcCcY7MVFdmXvk7Sb4DZhC\nm6qD\n'
Is stored in the database as:
'axZFZEknxUSYdUlPhwLBbj8CwSeCW5at2INA98EcCcY7MVFdmXvk7Sb4DZhC
m6qD'
The problem with this is that this then fails:
thing = things(:one)
assert_equal thing, Thing.find_by_secret(thing.secret)
Thing.find_by_secret(thing.secret) returns nil because the resulting SQL query tries to match the two versions of the encryped secret and fails to get a match.
I have tried:
one:
encrypted_secret: 'axZFZEknxUSYdUlPhwLBbj8CwSeCW5at2INA98EcCcY7MVFdmXvk7Sb4DZhC\nm6qD\n'
but get the same result.
How can I configure my fixtures to work with attr_encrypted?

A solution that works is to replace all '\n' with '\\n' and use double quotes. This works:
one:
encryped_secret: "<%= Thing.encrypt_secret(SecureRandom.uuid).gsub(/\n/, '\\\\n') %>"
Is there a tidier way to do this?

I faced the same situation under Rails4 + attr_encrypted + fixture + Minitest environment, and here my workaround is.
In summary, I had the following steps:
write plain (= unencrypted) text fixture with a specific file extention (in my case, it is *.yml.noenc).
write rake-task to convert from the plain fixture (.yml.noenc) to encrypted fixture (.yml).
Let me explain the detail below.
For example, "Message" model has two attributes 'name' and 'body' which are required to be encrypted as follows:
class Message < ActiveRecord::Base
attr_encrypted :name, key: ...
attr_encrypted :body, key: ...
...
end
write test/fixtures/messages.yml.noenc as follows, which has plain name and body text:
msg1:
name: Hello
body: Hello, I am here...
msg2:
name: How are you
body: Good morning, ...
write like the following rake-task (e.g. lib/tasks/encrypt_fixture.rake) to convert messages.yml.noenc to messages.yml:
require 'active_record/fixtures'
src_yml = 'test/fixtures/messages.yml.noenc'
dest_yml = 'test/fixtures/messages.yml'
task 'test' => dest_yml
namespace :[MY_APP] do
desc "generate encrypted fixture"
file dest_yml => src_yml do |t|
require Rails.root + 'config/environment'
encrypted_hash = {}
for k, v in YAML.load(ERB.new(File.read(Rails.root + src_yml)).result) do
msg = Message.new(v.merge([ANY ADDITIONAL ATTRS]))
encrypted_hash[k] = {
'encrypted_name' => msg.encrypted_name,
'encrypted_name_iv' => msg.encrypted_name_iv,
'encrypted_body' => msg.encrypted_body,
'encrypted_body_iv' => msg.encrypted_body_iv,
[ANY ADDITIONAL KEY_N_VALUE]
}
end
File.open(Rails.root + t.name, 'w') do |f|
f.write(<<EOH)
#----------------------------------------------------------------------
# DO NOT MODIFY THIS FILE!!
#
# This file is generated from #{src_yml} by:
#
# (edit #{src_yml})
# $ rake [MY_APP]:generate_fixture, or
# $ rake
#----------------------------------------------------------------------
EOH
f.write(encrypted_hash.to_yaml)
end
end
end
Please substitute [MY_APP], [ANY ADDITIONAL ATTRS], and [ANY ADDITIONAL KEY_N_VALUE] to actual values.
Then, 'rake' or 'rake test' checks file dependency between messages.yml.noenc and messages.yml, and generate messages.yml when necessary before 'rake test'.

Related

Improper indentation in converting ruby hash to yaml

I am trying to convert ruby hash object to YAML format using YAML.dump(obj) but I am getting improper indentation even after using dump options.
I have below executable ruby script :
#!/usr/bin/ruby
require "yaml"
require "erb"
context_path = ARGV[0]
context = YAML.load_file(context_path)['context']
def get_yaml(obj)
YAML.dump( obj['imports']['external_repositories']['credentials'] ).sub(/.*?\n/,'')
end
The value of - obj['imports']['external_repositories']['credentials'] is
{"iacbox"=>{"basic"=>{"name"=>"", "password"=>""}}, "nexus"=>{"basic"=>{"name"=>"cpreader", "password"=>"swordfish"}}}
Note : I used the sub method to remove "---" at the start of the output
The ERB template calls the above get_yaml method as :
credentials:
<%= get_yaml( context ) %>
The output that is coming is :
credentials:
iacbox:
basic:
name: ''
password: ''
nexus:
basic:
name: cpreader
password: swordfish
while I am expecting the output as :
credentials:
iacbox:
basic:
name: ''
password: ''
nexus:
basic:
name: cpreader
password: swordfish
How can I get the expected output from a dump?
I think the easiest thing for you to do here is just put the credentials key also in the Hash, i.e. change your template snippet so that it is one line:
<%= get_yaml( context ) %>
And change your get_yaml method to be:
def get_yaml(obj)
YAML.dump({'credentials' => obj['imports']['external_repositories']['credentials']})
.sub(/.*?\n/,'')
end
If that doesn't work for you, for example, if you have additional keys underneath the credentials key that you haven't mentioned, you could also do something like this:
def get_yaml(obj)
YAML.dump(obj['imports']['external_repositories']['credentials'])
.sub(/^---\n/,'')
.gsub(/\n/m,"\n ")
end
Where gsub(/\n/m,"\n ") replaces all newlines with a newline plus two spaces.

Extract new keys from .yml file using Ruby and git

I have a Rails project with a .yml file containing I18n translation keys. I want to create a rake task (or similar) which extracts the paths for added keys (lines git recognizes as added). It doesn't matter if the result is being written to the terminal or a file.
Example .yml file:
en:
index: # <-- new key
greeting: "Hello world!" # <-- new key
show:
title: "Old text"
body: "This is a text" # <-- new key
Example output/result of rake task:
en.index.greeting
en.show.body
Is this somehow possible? Thanks!
Yes, you can. This function will print all of I18n keys
def print_translations(prefix, x)
if x.is_a? Hash
prefix += "." if prefix.present?
x.each do |key, value|
print_translations(prefix + key.to_s, value)
end
else
puts prefix
end
end
I18n.translate(:foo)
translations_hash = I18n.backend.send :translations
print_translations "", translations_hash

Ruby hexdigest sha1 pack('H*') string encoding...

I meet an encoding problem... No errors in the console, but the output is not well encoded.
I must use Digest::SHA1.hexdigest on a string and then must pack the result.
The below example should outputs '{´p)ODýGΗ£Iô8ü:iÀ' but it outputs '{?p)OD?GΗ?I?8?:i?' in the console and '{�p)OD�G^BΗ�I�8^D�:i�' in the log file.
So, my variable called pack equals '{?p)OD?GΗ?I?8?:i?' and not '{´p)ODýGΗ£Iô8ü:iÀ'. That's a big problem... I'm doing it in a Rails task.
Any idea guys?
Thanks
# encoding: utf-8
require 'digest/sha1'
namespace :my_app do
namespace :check do
desc "Description"
task :weather => :environment do
hexdigest = Digest::SHA1.hexdigest('29d185d98c984a359e6e6f26a0474269partner=100043982026&code=34154&profile=large&filter=movie&striptags=synopsis%2Csynopsisshort&format=json&sed=20130527')
pack = [hexdigest].pack("H*")
puts pack # => {?p)OD?GΗ?I?8?:i?
puts '{´p)ODýGΗ£Iô8ü:iÀ' # => {´p)ODýGΗ£Iô8ü:iÀ
end
end
end
This is what I did (my conversion from PHP to Ruby)
# encoding: utf-8
require 'open-uri'
require 'base64'
require 'digest/sha1'
class Allocine
$_api_url = 'http://api.allocine.fr/rest/v3'
$_partner_key
$_secret_key
$_user_agent = 'Dalvik/1.6.0 (Linux; U; Android 4.2.2; Nexus 4 Build/JDQ39E)'
def initialize (partner_key, secret_key)
$_partner_key = partner_key
$_secret_key = secret_key
end
def get(id)
# build the params
params = { 'partner' => $_partner_key,
'code' => id,
'profile' => 'large',
'filter' => 'movie',
'striptags' => 'synopsis,synopsisshort',
'format' => 'json' }
# do the request
response = _do_request('movie', params)
return response
end
private
def _do_request(method, params)
# build the URL
query_url = $_api_url + '/' + method
# new algo to build the query
http_build_query = Rack::Utils.build_query(params)
sed = DateTime.now.strftime('%Y%m%d')
sig = URI::encode(Base64.encode64(Digest::SHA1.digest($_secret_key + http_build_query + '&sed=' + sed)))
return sig
end
end
Then call
allocine = Allocine.new(ALLOCINE_PARTNER_KEY, ALLOCINE_SECRET_KEY)
puts allocine.get('any ID')
get method return 'e7RwKU9E%2FUcCzpejSfQ4BPw6acA%3D' in PHP and 'cPf6I4ZP0qHQTSVgdKTbSspivzg=%0A' in Ruby...
thanks again
I think this "encoding" issue has turned up due to debugging other parts of a conversion from PHP to Ruby. The target API that will consume a digest of params looks like it will accept a signature variable constructed in Ruby as follows (edit: well this is guess, there may also be relevant differences between Ruby and PHP in URI encoding and base64 defaults):
require 'digest/sha1'
require 'base64'
require 'uri'
sig_data = 'edhefhekjfhejk8edfefefefwjw69partne...'
sig = URI.encode( Base64.encode64( Digest::SHA1.digest( sig_data ) ) )
=> "+ZabHg22Wyf7keVGNWTc4sK1ez4=%0A"
The exact construction of sig_data from the parameters that are being signed is also important. That is generated by the PHP method http_build_query, and I do not know what order or escaping that will apply to input params. If your Ruby version gets them in a different order, or escapes differently to PHP, the signature will be wrong (edit: Actually it is possible we are looking here for a signature on the exact query string sent the API - I don't know). It is possibly an issue of that sort that has led you down the rabbit hole of how the signature is constructed?
Thank you guys for your help.
Problem is solved. With the following code I obtain exactly the same string as with PHP:
http_build_query = Rack::Utils.build_query(params)
sed = DateTime.now.strftime('%Y%m%d')
sig = CGI::escape(Base64.strict_encode64(Digest::SHA1.digest($_secret_key + http_build_query + '&sed=' + sed)))
Now I've another problem for which I opened a new question here.
thanks you very much.

ruby net_dav sample puts

I'm trying to put a file on a site with WEB_DAV. (a ruby gem)
When I follow the example, I get a nil exception
#### GEMS
require 'rubygems'
begin
gem "net_dav"
rescue LoadError
system("gem install net_dav")
Gem.clear_paths
end
require 'net/dav'
uri = URI('https://staging.web.mysite');
user = "dave"
pasw = "correcthorsebatterystaple"
dav = Net::DAV.new(uri, :curl => false)
dav.verify_server = false
dav.credentials(user, pasw)
cargo = ("testing.txt")
File.open(cargo, "rb") { |stream|
dav.put(urI.path +'/'+ cargo, stream, File.size(cargo))
}
when I run this I get
`digest_auth': can't convert nil into String (TypeError)
this relates to line 197 in my nav.rb file.
request_digest << ':' << params['nonce']
So what I'm wondering is what step did I not add?
Is there a reasonable example of the correct use of this gem? Something that does something that works would be sweet :)
SIDE QUESTION: Is this the correct gem to use to do web_DAV? It seems an old unmaintained gem, perhaps there's something used by more to accomplish the task?
Try referencing the hash with a symbol rather than a string, i.e.
request_digest << ':' << params[:nonce]
In a simple test
baz = "baz"
params = {:foo => "bar"}
baz << ':' << params['foo']
results in the same error as you're getting.

I18n: How to check if a translation key/value pairs is missing?

I am using Ruby on Rails 3.1.0 and the I18n gem. I (am implementing a plugin and) I would like to check at runtime if the I18n is missing a translation key/value pairs and, if so, to use a custom string. That is, I have:
validates :link_url,
:format => {
:with => REGEX,
:message => I18n.t(
'custom_invalid_format',
:scope => 'activerecord.errors.messages'
)
}
If in the .yml file there is not the following code
activerecord:
errors:
messages:
custom_invalid_format: This is the test error message 1
I would like to use the This is the test error message 2. Is it possible? If so, how can I make that?
BTW: For performance reasons, is it advisable to check at runtime if the translation key/value pairs is present?
You could pass a :default parameter to I18n.t:
I18n.t :missing, :default => 'Not here'
# => 'Not here'
You can read more about it here.
I just had the same question and I want to compute an automatic string in case the translation is missing. If I use the :default option I have to compute the automatic string every time even when the translation is not missing. So I searched for another solution.
You can add the option :raise => true or use I18n.translate! instead of I18n.translate. If no translation can be found an exception is raised.
begin
I18n.translate!('this.key.should.be.translated', :raise => true)
rescue I18n::MissingTranslationData
do_some_resource_eating_text_generation_here
end
I don't know how to this at runtime but you can use rake to find it out. You'll have create your own rake task for that. Here's one:
namespace :i18n do
desc "Find and list translation keys that do not exist in all locales"
task :missing_keys => :environment do
def collect_keys(scope, translations)
full_keys = []
translations.to_a.each do |key, translations|
new_scope = scope.dup << key
if translations.is_a?(Hash)
full_keys += collect_keys(new_scope, translations)
else
full_keys << new_scope.join('.')
end
end
return full_keys
end
# Make sure we've loaded the translations
I18n.backend.send(:init_translations)
puts "#{I18n.available_locales.size} #{I18n.available_locales.size == 1 ? 'locale' : 'locales'} available: #{I18n.available_locales.to_sentence}"
# Get all keys from all locales
all_keys = I18n.backend.send(:translations).collect do |check_locale, translations|
collect_keys([], translations).sort
end.flatten.uniq
puts "#{all_keys.size} #{all_keys.size == 1 ? 'unique key' : 'unique keys'} found."
missing_keys = {}
all_keys.each do |key|
I18n.available_locales.each do |locale|
I18n.locale = locale
begin
result = I18n.translate(key, :raise => true)
rescue I18n::MissingInterpolationArgument
# noop
rescue I18n::MissingTranslationData
if missing_keys[key]
missing_keys[key] << locale
else
missing_keys[key] = [locale]
end
end
end
end
puts "#{missing_keys.size} #{missing_keys.size == 1 ? 'key is missing' : 'keys are missing'} from one or more locales:"
missing_keys.keys.sort.each do |key|
puts "'#{key}': Missing from #{missing_keys[key].join(', ')}"
end
end
end
put the given in a .rake file in your lib/tasks directory and execute:
rake i18n:missing_keys
Information source is here and code on github here.
If you wish to pass variable to the message like This is the test error message {variable}
This is possible using variable in language file like below.
# app/views/home/index.html.erb
<%=t 'greet_username', :user => "Bill", :message => "Goodbye" %>
# config/locales/en.yml
en:
greet_username: "%{message}, %{user}!"
More description you can find here.

Resources