Weird Bytes Added to Attribute After Save in Rails - ruby-on-rails

We're experiencing an insane bug where seemingly random bytes are some 90% of the time being tacked on to an email field right at the point when the email is being saved. Here's an example of what might occur:
From params: 'user#example.com'
Before validate: 'user#example.com'
After validate: 'user#example.com'
Before save: 'user#example.com'
Value in object after save: 'user#example.com'
Retrieve record just created by id, and fetch id: 'user#example.com\u007f'
Where the heck did that \u007f (the UTF-8 delete character!!!) come from?! That's by far the most common garbage that shows up. Here's a list some other valid byte sequences that have appeared from time to time:
r\u007f
U\u007f
a\u007f
#m$\u007f
Sometimes I get totally garbage bits, I can't tell if there are more bytes than these due to a PG::CharacterNotInRepertoire error:
0xde 0x4d
0xf6 0x7f
0xbc
0xe3 0x6c 0x24
Given the PG::CharacterNotInRepertoire errors that occur, I'm assuming this is happening somewhere immediately before the value is being saved, but outside of the scope of my application code.
Note that this is strangely not happening for any other fields for the user.
Here are all the callbacks that currently touch the email address:
#strip! and #downcase! before validation
Format validation with the regex \A[A-Za-z0-9._%+-]+#(?:[A-Za-z0-9-]+\.)+[A-Za-z]{2,20}\z
Some app info:
Ruby v2.2.0
Rails v4.1.8
Postgres v9.3.2
PG v0.17.1

Turns out that pg-ruby < v0.18.0 is incompatible with Ruby v2.2 despite there being no obvious warnings to the contrary...
https://bitbucket.org/ged/ruby-pg/issue/210/crazy-bytes-being-added-to-record
Upgrade now or get bit(s).

Also be aware that if you're using Rails 4.2.0, there is an issue with pg 0.18.* that affects writing binary data. I currently have 4.2.0 monkey-patched with the the patch that will be in 4.2.1. See https://github.com/rails/rails/pull/17680 for details if you are running 4.2.0 with Ruby 2.2 (and thus with pg 0.18.). There may be similar issues on Rails 4.0. and 4.1.* - I haven't figured out which version have the patch, and whether those versions have been released yet.
My monkey-patch for 4.2.0 looks like:
# Should release in Rails 4.2.1
# PostgreSQL, Fix change detection caused by superfluous bytea unescaping
# See https://github.com/rails/rails/pull/17680
if Rails.version == '4.2.0'
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
class Bytea < Type::Binary # :nodoc:
def type_cast_from_database(value)
return if value.nil?
return value.to_s if value.is_a?(Type::Binary::Data)
PGconn.unescape_bytea(super)
end
end
end
end
end
end
end

Related

Why does BCrypt no longer accept hashes?

Last week I upgrade Fedora to the brand new 28 release, which came with a mongodb upgrade to 3.6. See How to repair mongodb service after an upgrade to Fedora 28? for how I managed to resolve my first problem which was that mongod would no longer start. Now I'm facing an other problem on the Rails application that use this same database.
This most probably is unrelated to the mongodb upgrade, but I thought it might worth providing that context and don't miss a solution for not providing enough of it.
So since the system upgrade any login attempt on this Rails project will fail with a BCrypt::Errors::InvalidHash in Devise::SessionsController#create
error, raised at bcrypt (3.1.11) lib/bcrypt/password.rb:60:ininitialize'`. Analyzing further in a Rails console of the project, it seems any call to this method will fail:
> BCrypt::Password.create('TestPassword')
BCrypt::Errors::InvalidHash: invalid hash
from /home/psychoslave/.rbenv/versions/2.4.3/lib/ruby/gems/2.4.0/gems/bcrypt-3.1.11/lib/bcrypt/password.rb:60:in `initialize'
I tried to bundle uninstall/reinstall bcrypt, and even use the github repository version of the bcrypt gem instead, but it didn't change anything.
Looking at /home/psychoslave/.rbenv/versions/2.4.3/lib/ruby/gems/2.4.0/gems/bcrypt-3.1.11/lib/bcrypt/password.rb:60:ininitialize'`, the problem seems that the hash is not valid.
# Initializes a BCrypt::Password instance with the data from a stored hash.
def initialize(raw_hash)
if valid_hash?(raw_hash)
self.replace(raw_hash)
#version, #cost, #salt, #checksum = split_hash(self)
else
raise Errors::InvalidHash.new("invalid hash")
end
end
And the corresponding test is as follow:
def valid_hash?(h)
h =~ /^\$[0-9a-z]{2}\$[0-9]{2}\$[A-Za-z0-9\.\/]{53}$/
end
The hash itself is created through BCrypt::Engine.hash_secret(secret, BCrypt::Engine.generate_salt(cost)), which in the platform I use call __bc_crypt(secret.to_s, salt), which seems to be calling bcrypt-3.1.11/ext/mri/bcrypt_ext.c.
More importantly, adding a binding.pry in the valid_hash? method, it's possible to see what the hash value returned for a call to BCrypt::Password.create('TestPassword'), it's actually a rather long string whose start seems usual, but end up with what is most likely misgenerated sequence:
"$2a$10$Eb1f8DSkGh4G1u5GicyTYujBk6SwFXKYCH.nqxapmBlqJ0eFYdX32\x00\x00\x00\x00\xD1F\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00T\xBD\x02\x00\x00\x00\x00\x00\xF1V\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\xE2\xB0\x02\x00\x00\x00\x
00\x00AW\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00 \x04\x00\x00\x00\x00\x00\x00\x86\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\xB5\xF8\x0E\x00\x00\x00\x00\x00q\xD8\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00…"
I can provide a dump of a whole hash if it might be of any interest (around 32Ko!).
Here is a circumvent solution which make rspec of bcrypt pass all tests successfully again.
This is really a uggly hack while waitting a proper solution, but does the job until then. Just change ~/.rbenv/versions/2.4.3/lib/ruby/gems/2.4.0/gems/bcrypt-3.1.11/lib/bcrypt/engine.rb (path to adapt, of course), line 51 from:
- __bc_crypt(secret.to_s, salt)
+ __bc_crypt(secret.to_s, salt).gsub(/(\n|\x00).*/, '')
That is, trunk the string starting at the first "\x00" or "\n" occurrence, if any.
Credit note: this version of the hack was proposed by Andrey Sitnik, and I replaced the one I proposed here independently, before discovering it.
After that, BCrypt::Password#create will function again:
> BCrypt::Password.create('TestPassword')
=> "$2a$10$YPRnQF3ZihXHpa9kSx7Mpu.j28PlbdwaNs2umSQvAGkS.JJ.syGye"
I had this issue with a (very) old application and BCrypt 3.1.10. Upgrading to 3.1.12 resolved the issue. :)

invalid salt (BCrypt::Errors::InvalidSalt)

Since upgraded to Ruby 2.2.0 I get the following message in my tests:
invalid salt (BCrypt::Errors::InvalidSalt)
I didn't find any upgrade notice helping me to understand the problem. I'm using Rails 4.1.8 and Sorcery 0.8.6.
Anybody else having this problem?
MORE Details:
I'm using Sorcery and not Devise. The encrypted data is the password.
It all started in Cucumber tests, in 2 cases:
When I used to send the #user to the mailer to prepare the data for the mails. Here was the code:
UserMailer.passphrase_reset_notification(#user).deliver
Which generated the exception with the message I wrote in the initial message. As a workaround instead of sending the #user I sent the fields I needed and it worked. Here's the new code:
UserMailer.passphrase_reset_notification(#user.name, #user.email).deliver
But the second case is the sign up. It failed in dev and I had to add :salt to user_params to fix it. But it does not fix the thing in the test env.
There's no stack trace, just that one liner message with the lines of my scenario leading to the error.
And I press "Sign up"
invalid salt (BCrypt::Errors::InvalidSalt)
./app/controllers/users_controller.rb:66:in block in create'
./app/controllers/users_controller.rb:64:increate'
./app/controllers/application_controller.rb:120:in scope_current_tenant'
./features/step_definitions/web_steps.rb:53:in/^(?:|I )press "([^"]*)"$/'
features/users/sign_up.feature:149:in `And I press "Sign up"'
I removed the "null: false" for the field "salt" in the user table, as suggested by a community member in a post on a more or less similar issue, it didn't help either.
My main question is still the same: what the Ruby new version (2.2.0) has to do with this? And what might be the other surprises if I upgrade the prod?
I just fixed this. Turned out it had to do with serializing an object with has_secure_password (which uses bcrypt-ruby)
More specifically, something like the following was causing the issue with Sidekiq as it tried to serialize arguments into objects for Redis queueing.
#user = User.new(
:firstname => 'Scott',
:lastname => 'Klein',
:password => 'mypass',
:password_confirmation => 'mypass'
)
#user.save!
# broken
# note that #user.password can still be called here
# and sidekiq will attempt to serialize this whole object using YAML
# and this is the serialization issue that barfs (in the depths of YAML)
UserMailer.delay.new_user_signup(#user)
# fixed
# i just passed the id and then recalled the user record in the mailer class
UserMailer.delay.new_user_signup(#user.id)
I've had similar problem. Investigation made me conclude that it's bcrypt not playing well with Psych (that's the Ruby system library for generating and parsing YAML).
There's an open bcrypt issue now. Waiting for gem author to fix it.
** FIXED **
The problem, at least mine, is fixed. I just upgraded the bcrypt gem from 3.1.
9 to 3.1.10 and it was it! Thanks Oleg to have created an issue on bcrypt account.

Rails 2.3.4 Geokit error?

I have a rails application. Rails version is 2.3.4 and ruby version 1.8.7. In that application I used geokit (1.5.0) gem and geokit-rails plugin to find nearest location.
And in my model I have code like this,
class Notary < ActiveRecord::Base
acts_as_mappable
end
And in my controller have code like this,
class Client::OrdersController < ApplicationController
def find_notary
#order = Order.find(params[:id])
#miles = 10
unless params[:notary_search]
#notaries = Notary.find(:all, :origin => #order.signing_location_zip_code, :within => #miles, :conditions => "on_vacation IS NOT true",:order=>"distance asc" )
else
some code
end
end
end
I am getting error in that line
#notaries = Notary.find(:all, :origin =>
#order.signing_location_zip_code, :within => #miles, :conditions =>
"on_vacation IS NOT true",:order=>"distance asc") like this,
Geokit::Geocoders::GeocodeError in
Client/ordersController#find_notary
Geokit::Geocoders::GeocodeError
RAILS_ROOT: /home/user/svnnew_app/trunk/app
Application Trace | Framework Trace | Full Trace
/home/user/.rvm/gems/ruby-1.8.7-p371#app/gems/geokit-1.5.0/lib/geokit/mappable.rb:282:in
`normalize'
/home/user/svnnew_app/trunk/app/vendor/plugins/geokit-rails/lib/geokit-rails/acts_as_mappable.rb:347:in
`normalize_point_to_lat_lng'
/home/user/svnnew_app/trunk/app/vendor/plugins/geokit-rails/lib/geokit-rails/acts_as_mappable.rb:306:in
`extract_origin_from_options'
/home/user/svnnew_app/trunk/app/vendor/plugins/geokit-rails/lib/geokit-rails/acts_as_mappable.rb:203:in
`prepare_for_find_or_count'
/home/user/svnnew_app/trunk/app/vendor/plugins/geokit-rails/lib/geokit-rails/acts_as_mappable.rb:108:in
`find'
/home/user/svnnew_app/trunk/app/app/controllers/client/orders_controller.rb:331:in
`find_notary'
Request
Parameters:
{"id"=>"1198"}
Show session dump Response
Headers:
{"Cache-Control"=>"no-cache", "Content-Type"=>""}
How to resolve this error? Please help me out..
I maintain geokit. I had a look at the source code and that line happens when the response back from the API isn't numeric, e.g. a string but not in the format '99.99, 88.88', so would happen if the geocoding API returned 'NA' or something for example.
And I know StackOverflow says "avoid" the following (clarification, opinion, etc.), but here it is:
I can't help further without you doing a few things:
1) If you could update to the latest geokit (gem: 1.7.1 or the latest github master)
2) Do some debugging, e.g. Open "/home/user/.rvm/gems/ruby-1.8.7-p371#app/gems/geokit-1.5.0/lib/geokit/mappable.rb" (line 282) and least "puts" the string that is causing the error along with any other relevant local variables.
Using gems like pry/pry-debugger are a must for any ruby developer to debugging such issues.
3) Update other software where possible (even upgrade to latest ruby 1.8.7 and rails 2.x)
You're ruby (1.8.7) and rails (2.x) both are quite old and contain various security issues which should be of some concern. I do my best to keep geokit/geokit-rails working with ruby 1.8.7, but even rails 2 compatibility was dropped in geokit-rails, although I'm happy to help because likely you issue is a problem in geokit-rails with supported rails versions.

Tracing dependency loading in Rails

Our team is working on a new application that we started with Rails 3.1 and Ruby 1.9.2 - and last night I took it to Ruby 1.9.3.
One of the gems we were using in the dependency chain (css_parser) ended up having a require 'iconv' in it, triggering a deprecation warning in 1.9.3 that looked like this:
.../gems/activesupport-3.1.1/lib/active_support/dependencies.rb:240:in `block in require': iconv will be deprecated in the future, use String#encode instead.
At first I naively blamed that on rails without a better trace, until I didn't find a require 'iconv' in it anywhere.
The only way I tracked this down was that I started commenting things out in my Gemfile and then I finally got the bright idea to load irb and start requiring each library in turn. I also could have just done a filesystem grep in the gems directory, but I wasn't exactly sure that "require 'iconv'" was what was triggering the error.
What a PITA. There has to be a better way - just doing a --trace in rake tasks that load rails didn't cut it. Is there some way / any way of triggering a trace on this that would have shown me which line in the relatively long list of library dependencies was triggering the deprecation?
So, it's probably a little moot because I'm not likely to ever run into the problem again (and the css_parser gem was the only iconv-requiring gems in my current Rails 3.1/Ruby 1.9.3 projects).
But it was a puzzle, so I wanted to find some way of solving it.
The problem is very iconv-specific in this case. There are other ruby deprecations, but for the most part they seem to go through Kernel#warn (if ruby) or rb_warn() (if C) - but the warning in iconv.c is a little different than the others - at any rate it's a puts to rb_stderr.
So maybe I can do the following
Override Kernel#require to capture stderr
Check for an iconv message after calling the original Kernel#require
Raise an exception if the message found, thereby getting a trace
Do this before bundler runs if at all possible.
It turns out I can't do #4 - because Bundler calls Kernel.require directly - but I can use Bundler to parse the Gemfile to give me a list of things to require myself.
So this is what I get - thanks to this stack overflow post for a pointer on capturing standard error - and the rubygems source for the idea on aliasing the original Kernel#require
# override Kernel#require to intercept stderr messages on require
# and raise a custom error if we find one mentioning 'iconv'
require "stringio"
class RequireError < StandardError
end
module Kernel
alias :the_original_require require
private :the_original_require
def capture_stderr
# The output stream must be an IO-like object. In this case we capture it in
# an in-memory IO object so we can return the string value. You can assign any
# IO object here.
previous_stderr, $stderr = $stderr, StringIO.new
yield
$stderr.string
ensure
# Restore the previous value of stderr (typically equal to STDERR).
$stderr = previous_stderr
end
def require(name)
captured_output = capture_stderr do
the_original_require(name)
end
if(captured_output =~ %r{iconv})
raise RequireError, 'iconv requirement found'
end
end
end
require 'bundler'
# load the list of Bundler requirements from the Gemfile
required_libraries = Bundler.definition.dependencies.map(&:name)
# loop through and require each, ignoring all errors other than
# our custom error
required_libraries.each do |requirement|
begin
require(requirement)
rescue Exception => e
if(e.class == RequireError)
raise e
end
end
end
And voila! A trace message that helps track down where the iconv requirement was.
In the end, probably just a search for "require 'iconv'" is still best (once it's clear that's the what was causing the error).
But, as in life. Some Yaks Must Be Shaved.
You could take a look at the Gemfile.lock file, which holds all dependencies in a hierarchical tree and indicates the versions required by each gem. This might help to identify the gem that is requiring it.

Ruby 1.9 doesn't support Unicode normalization yet

I'm trying to port over some of my old rails apps to Ruby 1.9 and I keep getting warnings about how "Ruby 1.9 doesn't support Unicode normalization yet." I've tracked it down to this function, but I'm getting about 20 warning messages per request:
rails-2.3.5/activesupport/lib/active_support/inflector.rb
def transliterate(string)
warn "Ruby 1.9 doesn't support Unicode normalization yet"
string.dup
end
Any ideas how I should start tracking these down and resolving it?
If you are aware of the consequences, i.e. accented characters will not be transliterated in Ruby 1.9.1 + Rails 2.3.x, place this in config/initializers to silence the warning:
# http://stackoverflow.com/questions/2135247/ruby-1-9-doesnt-support-unicode-normalization-yet
module ActiveSupport
module Inflector
# Calling String#parameterize prints a warning under Ruby 1.9,
# even if the data in the string doesn't need transliterating.
if Rails.version =~ /^2\.3/
undef_method :transliterate
def transliterate(string)
string.dup
end
end
end
end
Rails 3 does indeed solve this issue, so a more future-proof solution would be to migrate towards that.
The StringEx Gem seems to work pretty well. It has no dependency on Iconv either.
It adds some methods to the String class, like "to_ascii" which does beautiful transliteration out of the box:
require 'stringex'
"äöüÄÖÜßë".to_ascii #=> "aouAOUsse"
Also, the Babosa Gem does a great job transliterating UTF-8 strings, even with language support:
"Jürgen Müller".to_slug.transliterate.to_s #=> "Jurgen Muller"
"Jürgen Müller".to_slug.transliterate(:german).to_s #=> "Juergen Mueller"
Enjoy.
That method definition is wrapped in an if-statement for Ruby 1.9. Right above it, you will find the regular definition, which shows a bit more of what this is doing. It's a method used to convert accented characters into their regular variants. E.g.: á => a, or ë => e
But this method is only used in parameterize, which is in turn defined right above transliterate. This is all still in ActiveSupport. I can't find anything that is directly calling parameterize.
So perhaps you're using parameterize or transliterate yourself, somewhere in your Rails application?
Common usage (according to the parameterize documentation) is for creating friendly permalinks from arbitrary strings, much like SO does, for example:
http://stackoverflow.com/questions/2135247/ruby-1-9-doesnt-support-unicode-normalization-yet
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Replace the body of the method with
raise "transliterate called"
and observe a backtrace which will show you where the stuff is coming from at the first call. Your app will of course collapse as well but that will likely give you the culprit from the first try.
I appreciate that this is a dirty way to solve the problem, but having read the error message I'm aware of the issue. So I want to get rid of the warnings. I dropped this code in environment.rb:
module ActiveSupport
module Inflector
# Calling String#parameterize prints a warning under Ruby 1.9,
# even if the data in the string doesn't need transliterating.
# Maybe Rails 3 will have fixed it...?
if RAILS_GEM_VERSION =~ /^2\.3/
undef_method :transliterate
def transliterate(string)
string.dup
end
end
end
end
If you'd rather not monkey patch the Inflector module, you can also do this...
Both of the following worked for me to silence this annoying "Ruby 1.9 doesn't support Unicode normalization yet" warning:
silence_stream(STDERR) {
whatever_code_caused_transliterate_to_be_called
}
or
silence_warnings {
whatever_code_caused_transliterate_to_be_called
}
This does have the disadvantage that it requires cluttering up your calling code, but it is a technique you can use generally whenever you don't want to see warnings or other output.
activesupport provides silence_stream and silence_warnings in activesupport-2.3.11/lib/active_support/core_ext/kernel/reporting.rb
String#unicode_normalize, String#unicode_normalize!, String#unicode_normalized? will be introduced in Ruby 2.2. Sample code and implementation can be seen in test case, lib/unicode_normalize.rb and lib/unicode_normalize/normalize.rb.
// U+00E1: LATIN SMALL LETTER A WITH ACUTE
// U+U+0301: COMBINING ACUTE ACCENT
puts "\u00E1" == "a\u0301".unicode_normalize(:nfc)
puts true == "a".unicode_normalized?(:nfc)

Resources