How to restrict a user from updating certain fields with CanCanCan? - ruby-on-rails

So if I have a User, and he can create/update his Service, but he cannot :publish(bool) them, what would be the best solution to skip this field from the update_params hash?
I found this similar question, but it links to CanCan 2.0 which never got released, and I was wondering if there is a way to solve this problem using CanCanCan, but could not find anything in their documentation. Many thanks!

As far as I know you can't. I would have a seperate publish action. So I'd omit publish from the form and the service_params entirely.
Then I'd do something like this:
Class ServicesController
def publish
#service = Service.find(params[:id])
#service.update_column(:publish, true)
end
end
Then in ability
can :manage, Service
cannot :publish, Service
Alternatively you could do this (assuming you have a current user and something defining them as an admin)
def service_params
if current_user.admin?
params.require(:service).permit(:my_field, :publish)
else
params.require(:service).permit(:my_field)
end
end
So you'd be omitting it from the parameters if they're not an admin. In this case you'd probably want to hide the fields in the view dependent on whether or not they can change the field.

Related

spree customizing the create user sessions action

I added inheritance to my Spree::User model class with STI. I have a :type column which can be (Spree::Guest, Spree::Writer, or Spree::Reader).
In my authentication in the admin side I want to authenticate only writer and reader. What would be the best option to solve this issue?
I tried to override the create action to something like:
def create
authenticate_spree_user!
if spree_user_signed_in? && (spree_current_user.role?(:writer) || spree_current_user.role?(:reader))
respond_to do |format|
format.html {
flash[:success] = Spree.t(:logged_in_succesfully)
redirect_back_or_default(after_sign_in_path_for(spree_current_user))
}
format.js {
user = resource.record
render :json => {:ship_address => user.ship_address, :bill_address => user.bill_address}.to_json
}
end
else
flash.now[:error] = t('devise.failure.invalid')
render :new
end
end
In this case when trying to authenticate with user of type :guest, it redirects to the new action with invalid failure message (ok) but somehow the user get authenticated (nok).
I don't think that is a good way to solve that, controller should be just a controller. I'd rather go that way:
Spree uses cancancan (or cancan in older branches) for authorization and that's how Spree implements that. I don't know why you want that STI solution - I would simply create new custom Spree::Role for that but as I said I don't know why you chose STI way - that should work fine too.
Anyway, you can either just add a decorator for that ability file with additional checks for something like user.is_a? Spree::Guest and so on or register new abilities via register_ability - something like this.
Most important part of third link (or in case it goes off):
# create a file under app/models (or lib/) to define your abilities (in this example I protect only the HostAppCoolPage model):
Spree::Ability.register_ability MyAppAbility
class MyAppAbility
include CanCan::Ability
def initialize(user)
if user.has_role?('admin')
can manage, :host_app_cool_pages
end
end
end
Personally I would go with decorator option (code seems a bit unclear but is cleaner when it comes to determine what can be managed by who - remember about abilities precedence) but it is up to you. If you have any specific questions feel free to ask, I will help if I will be able to.
Edit: so if you want to disable authentication for some users maybe just leverage existing Devise methods? Something like this(in your user model):
def active_for_authentication?
super && self.am_i_not_a_guest? # check here if user is a Guest or not
end
def inactive_message
self.am_i_not_a_guest? ? Spree.t('devise.failure.invalid') : super # just make sure you get proper messages if you are using that module in your app
end

Using current user in Rails in a model method

I'm currently trying to implement simple audit for users (just for destroy method). This way I know if the user has been deleted by an admin or user deleted itself. I wanted to add deleted_by_id column to my model.
I was thinking to use before_destroy, and to retrieve the user info like described in this post :
http://www.zorched.net/2007/05/29/making-session-data-available-to-models-in-ruby-on-rails/
module UserInfo
def current_user
Thread.current[:user]
end
def self.current_user=(user)
Thread.current[:user] = user
end
end
But this article is from 2007, I'm not sure will this work in multithreaded and is there something more up to date on this topic, has anyone done something like this lately to pass on the experience?
Using that technique would certainly work, but will violate the principle that wants the Model unaware of the controller state.
If you need to know who is responsible for a deletion, the correct approach is to pass such information as parameter.
Instead of using callbacks and threads (both represents unnecessary complexity in this case) simply define a new method in your model
class User
def delete_user(actor)
self.deleted_by_id = actor.id
# do what you need to do with the record
# such as .destroy or whatever
end
end
Then in your controller simply call
#user.delete_user(current_user)
This approach:
respects the MVC pattern
can be easily tested in isolation with minimal dependencies (it's a model method)
expose a custom API instead of coupling your app to ActiveRecord API
You can use paranoia gem to make soft deletes. And then I suggest destroying users through some kind of service. Check, really basic example below:
class UserDestroyService
def initialize(user, destroyer)
#user = user
#destroyer = destroyer
end
def perform
#user.deleted_by_id = #destroyer.id
#user.destroy
end
end
UserDestroyService.new(user, current_user).perform

Rails current_user best practice

I'm on a dilema, a have a ton of objects associated to the current_user on my app. And and don't know if in my controllers i keep using the IDs to find these objects or put directly the current_user + object.
Exemple:
class HousesController < ApplicationController
def show
#house = House.find(params[:id]) **or?** #house = current_user.house
end
def edit
#house = House.find(params[:id]) **or?** #house = current_user.house
end
end
And this going on and on. thank's in advance
If you use House.find(params[:id]) you have a potential security hole, as a given user could simply change the number in the url and access the house for a different user. So if you go this route, you have to add something to protect unathorized access.
OTOH, current_user.house keeps them on their own house, but needs alternate code for admin functions.
For simple applications, you can do this by hand, but for larger applications, you might want to consider authorization frameworks such as cancan or declarative_authorization where you can more easily define the permissions.
I use decl_auth myself, and all my controllers either use its method of loading the resource with filter_resource_access (loads the appropriate resource or throws and error if not allowed) or by hand with House.with_permissions_to(:index) which will only give me a house if I have permission to load it.
As always, Railscasts say it best: cancan and declarative authorization.

How to apply permissions

I'm wondering how to implement some permission logic in a Rails 2.3.8 app.
For example, a user can only edit tasks the user created (aka "owns").
Should I do something like this:
user.can_edit_task(task)
or this:
task.can_be_edited_by?(user)
#Method needs to be passed in a user object (from controller). But then how
#can I access the current user in a "before_save" filter as below :
.
def User
before_save: check_permissions!
def check_permissions
#this way?
raise some_exception if task.can_be_edited_by?(user)
#or this way?
raise some_exception if self.can_edit_task?(task)
end
end
Any tips on how to go about this?
Please try to adopt some of existing plugins like cancan
It will save your life.

Rails3 - Permission Model Before_Save Check?

I have a permission model in my app, that ties (Users, Roles, Projects) together.
What I'm looking to learn how to do is prevent a user for removing himself for their project...
Can you give me feedback on the following?
class Permission < ActiveRecord::Base
.
.
.
#admin_lock makes sure the user who created the project, is always the admin
before_save :admin_lock
def before_save
#Get the Project Object
project = Find(self.project_id)
if project.creator_id == current_user.id
# SOME HOW ABORT OR SEND BACK Not Allowed?
else
#continue, do nothing
end
end
end
Is that look like the right approach?
Also, I'm not sure how to do the following two things above:
How to abort prevent the save, and send back an error msg?
Get the devise, current_user.id in the model, that doesn't seem possible, so how do Rails gurus do stuff like the above?
Thanks for reading through
How to abort prevent the save, and send back an error msg?
return false during the callback chain tells activemodel to stop (similar to how adding errors to the model during a validation tells it to stop at that point)
self.errors.add_to_base "msg" will add an error to the model, which can then be rendered on the view.
Get the devise, current_user.id in the model, that doesn't seem possible, so how do Rails gurus do stuff like the above?
Models shouldn't really know about things like the current request, if at all possible, you should be locking things down at the controller/action level.
EDIT:
So, the role of controllers is to deal with everything involved in getting the correct information together based on the request, and passing it to the view (which becomes the response). People often say "make your models fat and your controllers skinny", but that could be said of any system that embraces object oriented design -- your logic should be in objects when possible.
That being said, the whole point of controllers is to deal with routing the right things to the right places, and authentication is definitely a concern of routing.
You could easily move the line comparing creator_id to user id in the action, and react based on that.
Now, sometimes you genuinely need that stuff in the model and there is no way around it. That becomes a problem, because you need to fight rails to get it there. One way would be to attr_accessor a current_user field on your model, and pass that in on initialize. Another would be to remove the fields from the params hash that a user is not allowed to change in the action. Neither is really that nice though.
Agreed with Matt that you should try to use the controller for the redirect. The model should have the logic to determine if the redirect is appropriate. Maybe something like
class ProjectsController < ApplicationController
def update
redirect_to(projects_url, :alert => "You can't remove yourself from this project.") and return if Role.unauthorized_action?(:update, params[:project])
#project = Project.find(params[:id])
if #project.update_attributes(params[:project])
...
end
class Role
def self.unauthorized_action?(action, params)
# your logic here
end
You should check out CanCan for some ideas.
In permission model take one field project_creater as boolean
In project modelbefore_create :set_project_ownership
def set_project_ownership
self.permissions.build(user_id: User.current.id, project_creater: true)
end
In project controllerbefore_filter :set_current_user
In Application controllerdef set_current_user
User.current = current_user
end

Resources