Here's what I'm using. The token doesn't necessarily have to be heard to guess, it's more like a short url identifier than anything else, and I want to keep it short. I've followed some examples I've found online and in the event of a collision, I think the code below will recreate the token, but I'm not real sure. I'm curious to see better suggestions, though, as this feels a little rough around the edges.
def self.create_token
random_number = SecureRandom.hex(3)
"1X#{random_number}"
while Tracker.find_by_token("1X#{random_number}") != nil
random_number = SecureRandom.hex(3)
"1X#{random_number}"
end
"1X#{random_number}"
end
My database column for the token is a unique index and I'm also using validates_uniqueness_of :token on the model, but because these are created in batches automatically based on a user's actions in the app (they place an order and buy the tokens, essentially), it's not feasible to have the app throw an error.
I could also, I guess, to reduce the chance of collisions, append another string at the end, something generated based on the time or something like that, but I don't want the token to get too long.
-- Update EOY 2022 --
It's been some time since I answered this. So much so that I've not even taken a look at this answer for ~7 years. I have also seen this code used in many organizations that rely on Rails to run their business.
TBH, these days I wouldn't consider my earlier solution, or how Rails implemented it, a great one. Its uses callbacks which can be PITA to debug and is pessimistic 🙁 in nature, even though there is a very low chance of collision for SecureRandom.urlsafe_base64. This holds true for both long and short-lived tokens.
What I would suggest as a potentially better approach is to be optimistic 😊 about it. Set a unique constraint on the token in the database of choice and then just attempt to save it. If saving produces an exception, retry until it succeeds.
class ModelName < ActiveRecord::Base
def persist_with_random_token!(attempts = 10)
retries ||= 0
token = SecureRandom.urlsafe_base64(nil, false)
save!
rescue ActiveRecord::RecordNotUnique => e
raise if (retries += 1) > attempts
Rails.logger.warn("random token, unlikely collision number #{retries}")
token = SecureRandom.urlsafe_base64(16, false)
retry
end
end
What is the result of this?
One query less as we are not checking for the existence of the token beforehand.
Quite a bit faster, overall because of it.
Not using callbacks, which makes debugging easier.
There is a fallback mechanism if a collision happens.
A log trace (metric) if a collision does happen
Is it time to clean old tokens maybe,
or have we hit the unlikely number of records when we need to go to SecureRandom.urlsafe_base64(32, false)?).
-- Update --
As of January 9th, 2015. the solution is now implemented in Rails 5 ActiveRecord's secure token implementation.
-- Rails 4 & 3 --
Just for future reference, creating safe random token and ensuring it's uniqueness for the model (when using Ruby 1.9 and ActiveRecord):
class ModelName < ActiveRecord::Base
before_create :generate_token
protected
def generate_token
self.token = loop do
random_token = SecureRandom.urlsafe_base64(nil, false)
break random_token unless ModelName.exists?(token: random_token)
end
end
end
Edit:
#kain suggested, and I agreed, to replace begin...end..while with loop do...break unless...end in this answer because previous implementation might get removed in the future.
Edit 2:
With Rails 4 and concerns, I would recommend moving this to concern.
# app/models/model_name.rb
class ModelName < ActiveRecord::Base
include Tokenable
end
# app/models/concerns/tokenable.rb
module Tokenable
extend ActiveSupport::Concern
included do
before_create :generate_token
end
protected
def generate_token
self.token = loop do
random_token = SecureRandom.urlsafe_base64(nil, false)
break random_token unless self.class.exists?(token: random_token)
end
end
end
Ryan Bates uses a nice little bit of code in his Railscast on beta invitations. This produces a 40 character alphanumeric string.
Digest::SHA1.hexdigest([Time.now, rand].join)
This might be a late response but in order to avoid using a loop you can also call the method recursively. It looks and feels slightly cleaner to me.
class ModelName < ActiveRecord::Base
before_create :generate_token
protected
def generate_token
self.token = SecureRandom.urlsafe_base64
generate_token if ModelName.exists?(token: self.token)
end
end
There are some pretty slick ways of doing this demonstrated in this article:
https://web.archive.org/web/20121026000606/http://blog.logeek.fr/2009/7/2/creating-small-unique-tokens-in-ruby
My favorite listed is this:
rand(36**8).to_s(36)
=> "uur0cj2h"
If you want something that will be unique you can use something like this:
string = (Digest::MD5.hexdigest "#{ActiveSupport::SecureRandom.hex(10)}-#{DateTime.now.to_s}")
however this will generate string of 32 characters.
There is however other way:
require 'base64'
def after_create
update_attributes!(:token => Base64::encode64(id.to_s))
end
for example for id like 10000, generated token would be like "MTAwMDA=" (and you can easily decode it for id, just make
Base64::decode64(string)
This may be helpful :
SecureRandom.base64(15).tr('+/=', '0aZ')
If you want to remove any special character than put in first argument '+/=' and any character put in second argument '0aZ' and 15 is the length here .
And if you want to remove the extra spaces and new line character than add the things like :
SecureRandom.base64(15).tr('+/=', '0aZ').strip.delete("\n")
Hope this will help to anybody.
Try this way:
As of Ruby 1.9, uuid generation is built-in. Use the SecureRandom.uuid function.
Generating Guids in Ruby
This was helpful for me
you can user has_secure_token https://github.com/robertomiranda/has_secure_token
is really simple to use
class User
has_secure_token :token1, :token2
end
user = User.create
user.token1 => "44539a6a59835a4ee9d7b112b48cd76e"
user.token2 => "226dd46af6be78953bde1641622497a8"
To create a proper, mysql, varchar 32 GUID
SecureRandom.uuid.gsub('-','').upcase
def generate_token
self.token = Digest::SHA1.hexdigest("--#{ BCrypt::Engine.generate_salt }--")
end
I think token should be handled just like password. As such, they should be encrypted in DB.
I'n doing something like this to generate a unique new token for a model:
key = ActiveSupport::KeyGenerator
.new(Devise.secret_key)
.generate_key("put some random or the name of the key")
loop do
raw = SecureRandom.urlsafe_base64(nil, false)
enc = OpenSSL::HMAC.hexdigest('SHA256', key, raw)
break [raw, enc] unless Model.exist?(token: enc)
end
Related
I want to prevent reports where the values of distributed && checked are both nil || zero from being created.
I also want users to be able to delete existing reports by setting these values to either nil or zero in the form (reports are created and edited in batches and the UX leans towards setting the value to 0 or deleting the values from the form instead of a 'delete' button).
As a hobbyist, something about this feels like a bad idea:
class Report < ApplicationRecord
...
before_validation :prevent_meaningless_reports, if: (distributed.nil? || distributed.zero?) && (checked.nil? || checked.zero?)
...
...
private
def prevent_meaningless_reports
if new_record?
throw :abort
else #persisted?
self.destroy
end
end
end
It feels bad to be destroying a record during a before_* callback.
I'm risking asking a potentially opinion-based question because it seems like I might be violating some software principle that would tell me why this is a bad idea.
If this is acceptable behavior, is it better to do this in two blocks, one for before_create (for new records) and one for before_update (for persistent records)?
The issue you'll run into with this is when you trying to update a meaningless_report that already exists to perform further operations on it.
E.g:
meaningless_report.update(attribute: some_value)
meaningless_report.some_other_value #meaningless_report is already removed from the db and frozen
If you try to update the meaningless_report object again, you'll run into a RuntimeError. So this is definitely not a good idea
A better option is to use ActiveRecord validations, such that even before the reports are created, we validate them to ensure they are not meaningless and also ensure the records don't become meaningless during updates
E.g:
class Report < ApplicationRecord
validate :validate_meaningful_reports
def not_meaningful_report?
(distributed.nil? || distributed.zero?) && (checked.nil? || checked.zero?)
end
private
def validate_meaningful_reports
if not_meaningful_report?
errors.add(:base, "Report violates validations")
end
end
end
With this, we ensure that no meaningless report is created. To handle old meaningless reports, we can use a script or maybe a rake task for this. The script/task can look like this:
meaningless_reports = Report.unscoped.find_each { |r| r.destroy if r.not_meaningful_report?}
This is much safer
I'm trying to figure out how to obfuscate the ids of my records in rails.
For example: a typical path might look like http://domain/records/1, so it's pretty easy for people to deduce how much traffic the site is getting if they just create a new record.
One solution that I've used is to hash the id with a salt, but since I'm not sure whether that function is bijective, I end up storing it in another column in my database and double check for uniqueness.
Another option I was thinking about was generating a random hash and storing that as another column. If it isn't unique ... just generate another one.
What's the best way of doing this?
You could use the built-in OpenSSL library to encrypt and decrypt your identifiers, that way you would only need to overwrite to_param on your models. You'll also need to use Base64 to convert the encrypted data into plain text. I would stick this in a module so it can be reused:
require 'openssl'
require 'base64'
module Obfuscate
def self.included(base)
base.extend self
end
def cipher
OpenSSL::Cipher::Cipher.new('aes-256-cbc')
end
def cipher_key
'blah!'
end
def decrypt(value)
c = cipher.decrypt
c.key = Digest::SHA256.digest(cipher_key)
c.update(Base64.decode64(value.to_s)) + c.final
end
def encrypt(value)
c = cipher.encrypt
c.key = Digest::SHA256.digest(cipher_key)
Base64.encode64(c.update(value.to_s) + c.final)
end
end
So now your models would need to look something like this:
class MyModel < ActiveRecord::Base
include Obfuscate
def to_param
encrypt id
end
end
Then in your controller when you need to find a record by the encrypted id, you would use something like this:
MyModel.find MyModel.decrypt(params[:id])
If you're looking to encrypt/decrypt ids without storing them in the database, this is probably the easiest way to go.
Instead of numeric ids, use some kind of friendly url or human readable slug. There are lots of tools to choose from in this department. Not only are they more friendly to your users, but well chosen slugs can give a nice advantage with search engines.
Here's a gem that keeps it numeric, requires no database migrations, and no routing changes: https://github.com/namick/obfuscate_id
I've found that this gem doesn't work in concert with some other gems, notably paper_trail. This is because of the way it replaces the find method, and paper_trail causes find to be called with the actual record id.
So I've been using the gem's "scatter_swap" functionality, but not the rest of it. Here's the model:
require 'obfuscate_id/scatter_swap'
class Page < ActiveRecord::Base
# This is a random number that, if changed, will invalidate all existing URLs. Don't change it!
##obfuscate_spin = # random number here, which is essentially the encryption key
##
# Generate URL parameter to be used in the URL as the "id"
def to_param
# Use the obfuscate_id gem's class to "spin" the id into something obfuscated
spun_id = ScatterSwap.hash(self.id, ##obfuscate_spin)
# Throw any additional attributes in here that are to be included in the URL.
"#{spun_id} #{name}".parameterize
end
def self.find_by_slug!(slug)
spun_id = slug[/^[0-9]+/]
begin
find_by_id! ScatterSwap.reverse_hash(spun_id, ##obfuscate_spin)
rescue ActiveRecord::RecordNotFound => e
raise ActiveRecord::RecordNotFound, "Couldn't find matching Page."
end
end
end
And in the controller:
class PagesController < InheritedResources::Base
# Find the page using its URL slug
before_filter :find_page, except: [:index, :create, :new]
def find_page
#page = Page.find_by_slug! params[:id]
# If the URL doesn't match exactly, and this is a GET.
# We'll redirect to the new, correct URL, but if this is a non-GET, let's let them finish their request instead.
if params[:id] != #page.to_param && request.get?
redirect_to url_for({ id: #page.to_param }), status: 301
end
end
end
As an alternative to the redirection that takes place there, you could simply include a canonical URL in the page. The redirection has the bug of ignoring any query parameters in the URL. This was not a problem for my project, as I didn't have any. But a canonical URL would be better.
It's pretty easy to generate unique random identifiers for your records either using a randomized string generator or a simple call to Digest::SHA1.hexdigest which produces reasonably random and cryptographically unique results.
For instance, you can create a secondary column called ident or unique_id that stores your public identifiers. You can then over-write to_param to use this instead:
class MyModel < ActiveRecord::Base
before_create :assign_ident
def self.from_param(ident)
find_by_ident(ident)
end
def to_param
self.ident
end
protected
def assign_ident
self.ident = Digest::SHA1.hexdigest(SecureRandom.random_number(1<<256).to_s)
end
end
Theoretically there is a chance of collision on SHA1 but the odds are so astronomically low you're more liable to have a software crash because of a memory error or hardware malfunction. You can test this to see if it suits your needs by generating a few billion identities to see if they ever collide, which they shouldn't. A 256-bit random number should provide a sufficient amount of data for the SHA1 algorithm to chew on.
After reading through #siannopollo's post, I created a Gem based on the idea of his post (but with some improvements): https://github.com/pencil/encrypted_id
Just because it hasn't been mentioned here: You could simply use UUIDs (wikipedia article)
There are multiple ways of using UUID as primary keys in Rails, depending on your Rails version and database engine. It's easy to find.
Just as a possibility, in case you depend too much on your existing integer primary key, you can also just add a UUID to your table and make your model use it automatically when it comes to generating URLs by overwriting Model#to_param more details in the docs
This is probably one of the things that all new users find out about Rails sooner or later. I just realized that rails is updating all fields with the serialize keyword, without checking if anything really changed inside. In a way that is the sensible thing to do for the generic framework.
But is there a way to override this behavior? If I can keep track of whether the values in a serialized fields have changed or not, is there a way to prevent it from being pushed in the update statement? I tried using "update_attributes" and limiting the hash to the fields of interest, but rails still updates all the serialized fields.
Suggestions?
Here is a similar solution for Rails 3.1.3.
From: https://sites.google.com/site/wangsnotes/ruby/ror/z00---topics/fail-to-partial-update-with-serialized-data
Put the following code in config/initializers/
ActiveRecord::Base.class_eval do
class_attribute :no_serialize_update
self.no_serialize_update = false
end
ActiveRecord::AttributeMethods::Dirty.class_eval do
def update(*)
if partial_updates?
if self.no_serialize_update
super(changed)
else
super(changed | (attributes.keys & self.class.serialized_attributes.keys))
end
else
super
end
end
end
Yes, that was bugging me too. This is what I did for Rails 2.3.14 (or lower):
# config/initializers/nopupdateserialize.rb
module ActiveRecord
class Base
class_attribute :no_serialize_update
self.no_serialize_update = false
end
end
module ActiveRecord2
module Dirty
def self.included(receiver)
receiver.alias_method_chain :update, :dirty2
end
private
def update_with_dirty2
if partial_updates?
if self.no_serialize_update
update_without_dirty(changed)
else
update_without_dirty(changed | (attributes.keys & self.class.serialized_attributes.keys))
end
else
update_without_dirty
end
end
end
end
ActiveRecord::Base.send :include, ActiveRecord2::Dirty
Then in your controller use:
model_item.no_serialize_update = true
model_item.update_attributes(params[:model_item])
model_item.increment!(:hits)
model_item.update_attribute(:nonserializedfield => "update me")
etc.
Or define it in your model if you do not expect any changes to the serialized field once created (but update_attribute(:serialized_field => "update me" still works!)
class Model < ActiveRecord::Base
serialize :serialized_field
def no_serialize_update
true
end
end
I ran into this problem today and ended up hacking my own serializer together with a getter and setter. First I renamed the field to #{column}_raw and then used the following code in the model (for the media attribute in my case).
require 'json'
...
def media=(media)
self.media_raw = JSON.dump(media)
end
def media
JSON.parse(media_raw) if media_raw.present?
end
Now partial updates work great for me, and the field is only updated when the data is actually changed.
The problem with Joris' answer is that it hooks into the alias_method_chain chain, disabling all the chains done after (like update_with_callbacks which accounts for the problems of triggers not being called). I'll try to make a diagram to make it easier to understand.
You may start with a chain like this
update -> update_with_foo -> update_with_bar -> update_with_baz
Notice that update_without_foo points to update_with_bar and update_without_bar to update_with_baz
Since you can't directly modify update_with_bar per the inner workings of alias_method_chain you might try to hook into the chain by adding a new link (bar2) and calling update_without_bar, so:
alias_method_chain :update, :bar2
Unfortunately, this will get you the following chain:
update -> update_with_bar2 -> update_with_baz
So update_with_foo is gone!
So, knowing that alias_method_chain won't let you redefine _with methods my solution so far has been to redefine update_without_dirty and do the attribute selection there.
Not quite a solution but a good workaround in many cases for me was simply to move the serialized column(s) to an associated model - often this actually was a good fit semantically anyway.
There is also discussions in https://github.com/rails/rails/issues/8328.
I'm using this regex in my model to validate an URL submitted by the user. I don't want to force the user to type the http part, but would like to add it myself if it's not there.
validates :url, :format => { :with => /^((http|https):\/\/)?[a-z0-9]+([-.]{1}[a-z0-9]+).[a-z]{2,5}(:[0-9]{1,5})?(\/.)?$/ix, :message => " is not valid" }
Any idea how I could do that? I have very little experience with validation and regex..
Use a before filter to add it if it is not there:
before_validation :smart_add_url_protocol
protected
def smart_add_url_protocol
unless url[/\Ahttp:\/\//] || url[/\Ahttps:\/\//]
self.url = "http://#{url}"
end
end
Leave the validation you have in, that way if they make a typo they can correct the protocol.
Don't do this with a regex, use URI.parse to pull it apart and then see if there is a scheme on the URL:
u = URI.parse('/pancakes')
if(!u.scheme)
# prepend http:// and try again
elsif(%w{http https}.include?(u.scheme))
# you're okay
else
# you've been give some other kind of
# URL and might want to complain about it
end
Using the URI library for this also makes it easy to clean up any stray nonsense (such as userinfo) that someone might try to put into a URL.
The accepted answer is quite okay.
But if the field (url) is optional, it may raise an error such as undefined method + for nil class.
The following should resolve that:
def smart_add_url_protocol
if self.url && !url_protocol_present?
self.url = "http://#{self.url}"
end
end
def url_protocol_present?
self.url[/\Ahttp:\/\//] || self.url[/\Ahttps:\/\//]
end
Preface, justification and how it should be done
I hate it when people change model in a before_validation hook. Then when someday it happens that for some reason models need to be persisted with save(validate: false), then some filter that was suppose to be always run on assigned fields does not get run. Sure, having invalid data is usually something you want to avoid, but there would be no need for such option if it wasn't used. Another problem with it is that every time you ask from a model is it valid these modifications also take place. The fact that simply asking if a model is valid may result in the model getting modified is just unexpected, perhaps even unwanted. There for if I'd have to choose a hook I'd go for before_save hook. However, that won't do it for me since we provide preview views for our models and that would break the URIs in the preview view since the hook would never get called. There for, I decided it's best to separate the concept in to a module or concern and provide a nice way for one to apply a "monkey patch" ensuring that changing the fields value always runs through a filter that adds a default protocol if it is missing.
The module
#app/models/helpers/uri_field.rb
module Helpers::URIField
def ensure_valid_protocol_in_uri(field, default_protocol = "http", protocols_matcher="https?")
alias_method "original_#{field}=", "#{field}="
define_method "#{field}=" do |new_uri|
if "#{field}_changed?"
if new_uri.present? and not new_uri =~ /^#{protocols_matcher}:\/\//
new_uri = "#{default_protocol}://#{new_uri}"
end
self.send("original_#{field}=", new_uri)
end
end
end
end
In your model
extend Helpers::URIField
ensure_valid_protocol_in_uri :url
#Should you wish to default to https or support other protocols e.g. ftp, it is
#easy to extend this solution to cover those cases as well
#e.g. with something like this
#ensure_valid_protocol_in_uri :url, "https", "https?|ftp"
As a concern
If for some reason, you'd rather use the Rails Concern pattern it is easy to convert the above module to a concern module (it is used in an exactly similar way, except you use include Concerns::URIField:
#app/models/concerns/uri_field.rb
module Concerns::URIField
extend ActiveSupport::Concern
included do
def self.ensure_valid_protocol_in_uri(field, default_protocol = "http", protocols_matcher="https?")
alias_method "original_#{field}=", "#{field}="
define_method "#{field}=" do |new_uri|
if "#{field}_changed?"
if new_uri.present? and not new_uri =~ /^#{protocols_matcher}:\/\//
new_uri = "#{default_protocol}://#{new_uri}"
end
self.send("original_#{field}=", new_uri)
end
end
end
end
end
P.S. The above approaches were tested with Rails 3 and Mongoid 2.
P.P.S If you find this method redefinition and aliasing too magical you could opt not to override the method, but rather use the virtual field pattern, much like password (virtual, mass assignable) and encrypted_password (gets persisted, non mass assignable) and use a sanitize_url (virtual, mass assignable) and url (gets persisted, non mass assignable).
Based on mu's answer, here's the code I'm using in my model. This runs when :link is saved without the need for model filters. Super is required to call the default save method.
def link=(_link)
u=URI.parse(_link)
if (!u.scheme)
link = "http://" + _link
else
link = _link
end
super(link)
end
Using some of the aforementioned regexps, here is a handy method for overriding the default url on a model (If your ActiveRecord model has an 'url' column, for instance)
def url
_url = read_attribute(:url).try(:downcase)
if(_url.present?)
unless _url[/\Ahttp:\/\//] || _url[/\Ahttps:\/\//]
_url = "http://#{_url}"
end
end
_url
end
I had to do it for multiple columns on the same model.
before_validation :add_url_protocol
def add_url_protocol
[
:facebook_url, :instagram_url, :linkedin_url,
:tiktok_url, :youtube_url, :twitter_url, :twitch_url
].each do |url_method|
url = self.send(url_method)
if url.present? && !(%w{http https}.include?(URI.parse(url).scheme))
self.send("#{url_method.to_s}=", 'https://'.concat(url))
end
end
end
I wouldn't try to do that in the validation, since it's not really part of the validation.
Have the validation optionally check for it; if they screw it up it'll be a validation error, which is good.
Consider using a callback (after_create, after_validation, whatever) to prepend a protocol if there isn't one there already.
(I voted up the other answers; I think they're both better than mine. But here's another option :)
I have a model User and when I create one, I want to pragmatically setup some API keys and what not, specifically:
#user.apikey = Digest::MD5.hexdigest(BCrypt::Password.create("jibberish").to_s)
I want to be able to run User.create!(:email=>"something#test.com") and have it create a user with a randomly generated API key, and secret.
I currently am doing this in the controller, but when I tried to add a default user to the seeds.rb file, I am getting an SQL error (saying my apikey is null).
I tried overriding the save definition, but that seemed to cause problems when I updated the model, because it would override the values. I tried overriding the initialize definition, but that is returning a nil:NilClass and breaking things.
Is there a better way to do this?
use callbacks and ||= ( = unless object is not nil ) :)
class User < ActiveRecord::Base
before_create :add_apikey #or before_save
private
def add_apikey
self.apikey ||= Digest::MD5.hexdigest(BCrypt::Password.create(self.password).to_s)
end
end
but you should definitely take a look at devise, authlogic or clearance gems
What if, in your save definition, you check if the apikey is nil, and if so, you set it?
Have a look at ActiveRecord::Callbacks & in particular before_validation.
class User
def self.create_user_with_digest(:options = { })
self.create(:options)
self.apikey = Digest::MD5.hexdigest(BCrypt::Password.create("jibberish").to_s)
self.save
return self
end
end
Then you can call User.create_user_with_digest(:name => "bob") and you'll get a digest created automatically and assigned to the user, You probably want to generate the api key with another library than MD5 such as SHA256 you should also probably put some user enterable field, a continuously increasing number (such as the current date-time) and a salt as well.
Hope this helps
I believe this works... just put the method in your model.
def apikey=(value)
self[:apikey] = Digest::MD5.hexdigest(BCrypt::Password.create("jibberish").to_s)
end