I have a scenario where I am unsure of whether a particular function should be considered validation or authorization. I can code it either way.
Users can "like" articles.
When a user creates a new "like" I need to ensure the user has not already liked the article. The front end will limit the functionality however I want backed end safeguards.
Should the process of ensuring a user has not already liked the article be considered validation or authorization?
Further to comments received:
If auth determines if the option is available to the user, or not & validation determines if the user selection is valid then...
Auth will make the option to click "like" available even when then user has previously "liked" and therefore it will inevitably fail validation.
This thinking results in an invalid option being presented to the user.
Is ensuring the user can only delete/edit their own "likes" auth or validation? The previous logic implies it should be validation as the user is either authorised to add/update or destroy within the model or not and ensuring their actions are valid is the role of validation However it would be illogical to present the option to delete another user's like only to reject upon failed validation.
This is validation. I don't know your model architecture, but if you have a Like model, you could validate like this:
class Like < ActiveRecord::Base
belongs_to :user_id
belongs_to :article_id
validates :article_id, uniqueness: { scope: :user_id }
end
You should also make sure that a unique constraint is present at the DB level, to avoid a potential race condition.
This sounds more like validation. You have to check in your model that this article was liked by this user or not. If it is, then this like is invalid and he can't like it now. Otherwise, it will pass the validation and the user will be able to like this article.
Authorization should come, when some user can like some set of articles, but not all, in those situation, In my honest opinion.
a. should use rails validation to make sure he/she can like once not more then that.
b.authorization is to restrict user from hitting like.
authorization would be: is this user allowed to perform this action; validation: will this action succeed. given that user is allowed to 'like', ensuring he can do it only once is a validation problem. to solve it put unique constraint on db level (user_id, article_id).
Related
I am attempting to make a new record object invalid if the user_id of the object is associated with an account that is not admin.
In the model I'd like to make a validation checks if the object to be created user_id.admin == true.
Neither of these solutions work:
validates :user_id, User.find(:user_id).admin?
before_save :user_is_admin, User.find(self.user_id).admin?
So, my question is, how do I write a validation that looks up the user and checks if they are an admin, and throws an error if they are not?
P.S. I am already doing admin checking in the controller as a before_action, but I'd like to invalidate the object if a non admin user manages to create one somehow...and for testing purposes.
If this isn't a best practice I'd still like to know a bit more about creating validity checks in rails.
I generally wouldn't encourage doing this type of authorization at the model/database level. Rather I would suggest that you abstract your authorization code into it's own layer and handle this at the controller level, then rely on tests to verify that nobody can create an object except through the controller.
https://github.com/elabs/pundit is a great gem for integrating an authorization layer into your Rails application.
If you still did want to do this validation at the model level, you could do something like this:
validate :creator_is_admin
def creator_is_admin
errors[:base] << I18n.t('object_class.activerecord.validations.admin_create_check_failure') unless User.find(user_id).try(:admin?)
end
I mostly validating before action goes to the Model, controller could be good for it.
###Post Model Sample
validate :is_admin?
def is_admin?
unless User.find_by_id(user_id).admin
errors.add(:not_admin, "The post not belongs to you || not admin :) ")
end
end
In my Ruby on Rails application I am trying to add in validations that will ensure the user has entered a value in a text box. In my system I have a table called Account which stores users' email account information, when they go onto the views/accounts/_form.html.erb page to add a new email account I want to validate the presence of a port number. I can do this through the following code:
validates :port, presence: true
This successfully ensures that users enter their port number, but when a user signs up through the views/users/_new.html.erb page they have to enter only an email address (e.g example#example.com) and the users_controller will then create a record in the Account table for this email address. My problem is that on the views/accounts/_form.html.erb page the port number is required but on the views/users/_new.html.erb it is not.
Is there a way of validating that the user enters the port number if they are on the views/accounts/_form.html.erb page or invoking the create method in the accounts_controller?
I am aware that I could do this through the HTML required validation like so: <%= f.text_field :port, :required => true %> but I need to add in further validation as well as presence, so this is not suitable.
You can create an attr_accessor field that determines if the validation should occur...
class Account < ActiveRecord:Base
attr_accessor :port_needs_validation
validates :port, presence: true, if: -> {port_needs_validation}
Then just set the accessor in your create method...
def create
#account = Account.new
#account.assign_attributes(account_params)
#account.port_needs_validation = true
if #account.save
...
Extract that part of the logic into a form object, check out the legendary 2012 blog entry from CodeClimate. Things have changed since then, the author uses Virtus to build form objects, more popular & up-to-date gems these days are:
reform
dry-rb
active type
but really you can make anything behave like an ActiveModel object
if it's a one-off thing, just do what Steve said in the other answer but that is a sure way to hell, safe-hate and divorce (at least from personal experience) in any slightly teeny weeny bigger project (i.e. you mean to spend some hours more working on it, it's not like you just finished everything and want to go home).
Actually, just use form classes everywhere and avoid model validations & other callbacks at all. You don't want things sending account activation mails or validating your password complexity when you're writing tests and just need a "post" that belongs to "user".
My own favorite personal fuckup due to model callbacks is sending 240.000 "your account has been upgraded/downgraded" emails because of an innocent spelling change update in an account_type attribute migration just because account_type_changed? was true.
So.. Form classes for ever, model callbacks never.
I would not recommend you have model aware of views. In #SteveTurczyn 's solution, an abstract field is introduced into model to identified the which page it come from, which is an good solution.
As from Ruby on Rail MVC, both View and Model talk to the controller, another solution will be have controller handle validation of params before passing the value to create account.
The logic of the application that I currently work on demands a Payment mustn't be editable if its status is open. I see two ways of implementing this:
1 A routing constraint like:
constraint: lambda { |req| Payment.find(req.id).status != 'open' }
2 A simple condition in PaymentsController#edit:
if #payment.status == 'open'
redirect_to payments_path
end
What option should I go for? Which is more suitable and clean, Rails-ish? Is there any other option? If I go with the first option and have a resources :payments, how can I add the constraint only for the edit route?
As to the Rails way of solving this it is actually none of your suggestions.
Routing - You routes should just simply declaratively state the RESTful interface of your application. They should not be aware of the current request unless absolutely necessary.
Controller - Adding business logic in the controller will bloat your controllers and violates DRY.
In MVC the model is in charge of enforcing the business logic. You could handle this through a custom validation for example:
class Payment
validates :cannot_be_edited_when_open, on: :update
def cannot_be_edited_when_open
errors.add(:status, 'is open. Cannot edit this record.') if self.open?
end
end
This will cause any call to .update to fail - which means that you probably will not need to change anything in your controller.
Edited
Another place to handle this would be on the authorization layer - the key difference here is how the feedback should be handled.
A validation failing will just re-render the form (422 Unprocessable Entity for an API) while an authorization error should clearly tell the user "no you don't have permission to do that - and changing the input won't change that" (403 Forbidden).
To setup the rule in CanCan you would do:
can [:edit, :update], Payment do |payment|
payment.status !== 'open'
end
You could also possibly set this up with hash condition instead of a block if your business logic allows it:
can [:edit, :update], Payment, status: 'not-open'
If the rule you specified is a business logic, i.e. no Payment object should be updated with the status open, then, the correct way would be to add that logic to your model. You can use before_validation or before_update callbacks. Also, don't display any means to edit (link, button, etc.) your Payments that have status open. If a user somehow gets to your form, display it, but then validation in your model will not let them save it. I think, in this case, everyone is responsible for their own responsibilities.
But, if you have to choose from the 2 options you provided, I would go with the second one. Your first option, having the business logic in routes, doesn't seem not right. It does not belong there, IMO. The second option is better, but the drawback is that you and your team members will still have to remember that they have to check the Payment object's status attribute does not have some value before touching it. Obviously, someone will forget to do that at some point. So, in the long run, your system (models in your system) will have a corrupted state.
I have 3 types of "users"
Shop
Provider
Customer
The last 2 users will have similar attributes such as
First Name
Last Name
and so on
And the Shop user type will have the most contrast between types.
As far as behaviours they will all be quite different, although Provider and Shop will inherent many of customer behaviours.
It seems the behaviours can be dealt with CanCan as I've researched.
I'm now attempting to how I should authenticate these types.
I have looked at the STI model but I couldn't grasp where I would these extra attributes.
My mental model is as follower:
User is a table and model
The types are abstract models that inherit from this.
So I'm wondering, how do I add attributes such as Business address for just the Shop type?
Or is it that the User Table has a column called Type and that type is associated with these type tables? And within the type tables are the extra attributes?
Don't even bother bringing Devise into this; Devise is for authentication not authorization. Authentication is determining whether or not someone who visits your site is who you think they are, like logging in. Authorization is deciding whether or not a user is allowed to perform some sort of action, like creating a new post.
What you want to do is have some sort of system that assigns a normal user your three different types; CanCan will do something like that. One way to do this on your own is using a permissions number based system. Let's say normal users have permissions level at 100, shop has a level at 50, and provider at 25. Using this system you can determine what actions a user can perform without having to make separate models, which will make your schema unnecessarily complicated. Here's an example of how this would work with say the UserController:
def show
if current_user.permissions == 100
render "customer_show"
elsif current_user.permissions == 50
render "shop_show"
else
render "provider_show"
end
end
The easiest way to do this is to add a column to the user's table called permissions that defaults to say 100 when a new row is created. Here's what that migration would look like:
def change
add_column :users, :permissions, :integer, default: 100
end
As for authenticating, don't worry about it. Let devise do it's thing; every user, no matter what type, will login and sign up in the same way, maybe just having separate forms for each that has a hidden field to set the permissions level for that specific kind of user.
I know I'm late to the party but I'm putting this out for future SO searchers. You CAN authorize actions with Devise. If you have devise models for 'buyer' & 'seller' you can add 'buyer_signed_in?' for whatever action you only want buyers to be able to do. You can also do more specific role-based authorizations as well - check out Devise' page
All in all, Tsiege's solution sounds pretty interesting. Let us know if you had any success with it!
There is so much written about the security threat of attr_accessible that I am beginning to wonder if I should even have any attributes in it. Here is the issue. I have a Message model which has the following:
attr_accessible :body,:sender_id,:recipient_id
I do not have the update or edit action in my messages_controller. With the new and create action I am able to create a new message and send it to a recipient. Only users who have logged in and meet certain conditions can message each other. I do that with the help of a before_filter and the conditions work fine. The message is stored and can be viewed by the sender and the recipient. Perfect!
The question I have is that since :body,:sender_id,:recipient_id are included in attr_accessible, can a malicious user somehow change the :body,:sender_id,:recipient_id of the original message? Should I just add these attributes to attr_readonly as well so they cannot be modified once saved?
This question has been haunting me for practically all my models.
can a malicious user somehow change the :body,:sender_id,:recipient_id
of the original message?
This would depend on other things rather than attr_accesible. attr_accesible will only filter which fields are allowed to be updated using mass assignment. Since you say you don't have any update action, then no, there is now way a user can edit a message since you always create a new Message through you create action.
But there is something you need to care about. What is sender_id? If you do have users in your app and they send messages to each others, then sender_id should not be an accessible field, since this will allow users to send messages on behalf of other users. You probably want to keep that field off the attr_accessible list and do something like this:
m = Message.new params[:message] # body and recipient_id
m.sender_id = current_user.id # this is not mass assignment
m.save
.....
Well, it depends on how your are creating your model's instance. If you use:
FooModel.create(params[:foo])
then yes, your are not secure because a logged in user may pass additional parameters to the request even if you don't provide explicitly form fields for those attributes.
So, for your case, anyone posting to your "create" action with sender_id, recipient_id (values in the request) will be able to change them unless you take care about this assignments in your action.