I want to protect rails model attributes from being changed, only after the model enter certain state (eg. sign_in_count > 1). Looked around and see people using
attr_readonly
to protect the attributes, can I use it with condition? If not, is there alternative solution? Thanks.
I don't really understand what you want to do, so here are some general but related purpose. Btw, in rails 4 the attributes are protected, but you can use protected_attributes gem to go through rails 3 behaviour.
Btw, in ruby you can use freeze method can be invoked to prevent from any modifications of some object you have frozen. (Toc check wether the object is indeed frozen, you can call frozen? on that object and this will return true or false).
Another remark is that you can check some change state of your model by calling changed—attributes, this returns a Hash of fields that changed relatively to their original values.
Edit : you can of course set this statement
attr_readonly :some, :attributes unless some_condition
and define
def some_condition
sign_in_count > 1
end
Related
In Rails 5 I have a model with an attribute, value of which is never set directly but is always calculated in before_save callback instead. I would like to protect this attribute from being updated from outside of the model, so that calls like f. e. update_attribute() would fail. I used attr_readonly inside the model to achieve what I want and it worked great until I realised that it prevents all updates! Also from within the model itself. Since, according to Rails API docs this is the correct behaviour, what would be the best way to reject modifications to a particular attribute but only from the outside?
You could override the setter. On the model:
def protected_attr_name=(val)
# raise SomeException
end
This disables:
model.protected_attr_name = "value" # => raise exception
model.update_attributes(protected_attr_name: "value") # => raise exception
And then in your before_save method/block:
write_attribute(:protected_attr_name, calculated_value)
Additional observations
Like attr_readonly, you could choose to not raise an exception, and not do anything instead. This may be confusing/frustrating to others working on the same codebase, and is potentially very non-obvious behaviour.
Also - if it is always calculated in the before_save, consider whether this protection is necessary, or whether clearer attribute naming can effectively make the issue go away.
So a situation came up at work and I wanted to discuss it here because we could not get to an agreement between us:
We have two models, Order and Passport, which are related in a way that an Order has_one passport and a passport has_many orders. Whenever an order is completed, its associated passport must be 'locked', that is, turned into read-only (that information was already used to clear customs, so it can't be changed afterwards). We want to enforce that rule in the Passport model and we've thought of the following options:
Creating a validation. CONS: There will be records yielding valid? => false when technically the record is fine (although it can't be saved). For example, if other records have a validates_associated :passport on them, that could be a problem.
Overriding the readonly? method. CONS: This will raise an exception when trying to update that record, although you would expect that calling a save method won't ever raise one.
Creating a before_save callback. This has two flavors: either raise an exception (which is pretty much like the readonly? option) or add an #error and return false to stop the callback chain. CONS: Adding validation errors from outside a proper validation can be considered a bad practice. Also, you might find yourself calling valid? and getting true and then call save and get false.
This situation made us think a lot about the relationship between validations and Rails. What exactly does it mean for a record to be valid?? Does it imply that the save will work?
I would like to listen to your opinions to learn about this scenario. Maybe the best approach is neither one of the three! Thanks!
What about marking this record as read-only by using readonly! instance method? See the API
You could do it in a constructor, like:
class Passport < ActiveRecord::Base
def initialize(*args)
super(*args)
readonly! if orders.count>0 # or similar
end
end
I think there is an extra alternative. What you describe dictates that the Passport model can have some different states. I would consider using a state machine to describe the relevant orders status for the passport.
eg:
open
pending
locked
other_update_actions ...
With that in mind, all relevant order actions will trigger an event to the passport model and its state.
If it is possible to integrate the update actions to certain events then you could handle the readonly part in a more elegant way (incompatible state transition).
As an extra check you can always keep an ugly validator as a last resort to prevent the model from being updated without the state machine.
you can check the aasm gem for this
I am implementing a User that is not actually deleted from the system with destroy but only marked with :active = false.
The problem here is that such an inactivate user will show up in all User.find, User.all, ... calls. I don't want to pollute the code with all kinds of 'if-else's or overwriting the behavior of .find, .all etc.
I just want to know whether I can nicely define it within the User's model so that inactive users will virtually disappear unless I explicitly want to extract such a user.
If there is no way to do it in the model then what are my options?
Use a scope, or a class method with a where clause.
I think you may want to check acts_as_paranoid Here is a link for one of the implementations: https://github.com/technoweenie/acts_as_paranoid
From the wiki:
Now whenever destroy is called on that model, it is just removed from view and the deleted_at column set to the current date time. All the finder methods ignore “deleted” records.
I wanted to start using attr_accessible with my models to stop the problem with mass assignment. I understand how it works and have researched as much as I could.
What I don't understand is the difference between using update_attributes(params[:my_form]) or create(params[:my_form]) and setting the fields one by one? Aren't both just as vulnerable?
What is the difference between NOT having attr_accessible and doing this...
#model_object = ModelObject.new
#model_object.create(params[:model_object_params])
And having attr_accessible and doing this...
#model_object = ModelObject.new
#model_object.field1 = params[:model_object_params][:field1]
#model_object.field2 = params[:model_object_params][:field2]
#model_object.field3 = params[:model_object_params][:field3]
#model_object.save!
Aren't both these methods of creating the record just as vulnerable? The hacker/cracker could send a url to both these methods and both would do just the same, right?
Or does using attr_accessible and updating the fields one-by-one do something different or somehow become safer?
There's where all these methods I'm finding of using attr_accessible don't make any sense to me. It seems to be doing the same thing two different ways. What am I missing?
Thanks.
In the way you are doing it, it does not prevent "mass assignment".
"Mass assignment" is the term used when Rails is handling the assigning of values to attributes in a model. This is typically done in a controller, using the names and values in params.
When you're doing the assigning yourself, it is also "mass assignment", in a way; but you have fine control over what to assign and what not to in this case. So, to save writing that boilerplate assignment code, Rails provides attr_accesible - same control, less code.
To see how it is used:
Presume that a ActivityLog model has an attribute called user_ip_address.
Now, user_ip_address is an attribute in the model, and could be assigned by mass-assignment or by "self-rolled-mass-assignment".
But in both cases that is wrong -- you don't want user-supplied input to set a value for that attribute.
Instead, you want to always find out the actual IP address of the user and assign that value (ignoring any
value in params). So you would exclude user_ip_address from attr_accessible and instead assign it yourself.
attr_accessible :all_attributes_except_user_ip_address
#al = ActivityLog.new(params[:model_object_params])
#al.user_ip_address = get_origin_user_ip_address
#al.save
For any information that a user should not be able to change, use attr_accessible and exclude it from the list.
The short answer is that it stops field4 from being set implicitly.
The difference is that without attr_accessible a hacker could update a field that is not in your form. With attr_accessible this impossible.
E.g. if your user model has a field is_admin, a hacker could try to create a new admin by posting:
params[:user][:is_admin] = true
If attr_accessible is set (and obviously it shouldn't contain is_admin) this is impossible.
About your example: if your model only has field1, field2 and field3 and there are no other database columns you want to protect, there is no need to use attr_accessible. Hope this makes it clear.
Just remember:
Without any precautions
Model.new(params[:model]) allows
attackers to set any database column’s
value.
Source: http://guides.rubyonrails.org/security.html#mass-assignment
The idea here is to limit the parameters you will accept for a given model. Then you can test each of them either with a validation or some other code to be sure they fit expected values.
Attr_accessible is intended to limit the "surface" of your model to what you intend to accept and check carefully. In this way you can automatically ignore an injected parameter like :roles => "admin" in case you add that feature to your model
user.update_attributes(params[:user])
Since the roles attribute is not listed in attr_accessible, the user's attempt to become an administrator is fruitless.
You want to handle the validation logic in one place (your model), instead of checking each parameter value in your controller.
Mass assignment isn't something you prevent, it's something you control. It's a good feature, one that makes things easier and cleaner, but without some kind of ability to control what gets set via mass assignment it's a potential security hole. attr_accessible, as others have mentioned, provides that control.
In my Rails application I'm trying to update a model's attribute using update_attribute in an after_create callback. I can successfully update the attribute, but for some reason all the model's other attributes are also updated when I do so. So, even though the model's name attribute (for example) has not changed it is set (to it's current value) in the database update query.
Is this the expected behaviour in Rails (2.3.8), or am I doing something wrong?
Yes I believe this is consistent behaviour because that instance of your model that was just created has not been reloaded. Therefore the 'changed' attributes have not been reset.
Sorry if that's not a very clear explanation. To demonstrate, run the debugger in your after_create method. E.g.
def my_after_save_callback
require 'ruby-debug'; debugger
update_attribute(:foo, "bar")
end
Then when the debugger starts run:
p self.changed
An array of all the attributes that have been modified for this object will be returned. ActiveRecord will update all these attributes the next time the object is saved.
One way around this is to reload the object before updating the attribute.
def my_after_save_callback
reload
update_attribute(:foo, "bar")
end
This will reset the 'changed' attributes and only the specific attribute you modify will be updated in the SQL query.
Hope that makes sense :-)