Cancan 2.0 - restricting permissions to a field - ruby-on-rails

I have the following in my ability model :
class Ability
include CanCan::Ability
#...
def superuser_rules
can :access, :items
cannot :update, :items
can :update, :items, :foo_attributes
end
end
I have a form which mirrors that by only displaying the foo_attributes nested form.
However, when submitting the form, it says the access is denied to update the item.
Is there a way to circumvent this without adding new routes/actions ?
Many thanks !

You can create new actions to handle these "special attributes".
First you can clean up the special attributes of the params.
class UserController
before_filter :only => [:create, :update] { params[:user].delete(:accepted_at) }
end
Then you create a special action to change a special attribute:
def accept
User.find(params[:user_id]).update_attributes :accepted_at => Time.now
end
Now you can set different permissions for create, update, and accept actions.
class Ability
include CanCan::Ability
def initialize(user)
if user && user.admin?
can :accept, User
elsif user
can :update, User
end
can :create, User
end
end
Take a look at this too

Related

CanCanCan :read block not working

I'm trying to put a very simple authorization on my Property class in Rails 5. I've added the can :read condition to ability.rb and used load_and_authorize_resource in my controller and I can't even get it to hit the pry, let alone authorize the :show action. Am I missing something obvious?
# ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
can :read, Property do |property|
binding.pry
PropertyUser.find_by(property_id: property.id, user_id: user.id)
end
end
end
# properties_controller.rb
class PropertiesController < ApplicationController
before_action :set_property, only: [:show, :edit, :update]
load_and_authorize_resource
skip_authorize_resource :only => [:new, :create]
def show
respond_to do |format|
format.html
format.js
end
end
private
def set_property
#property = Property.find(params[:id])
end
end
Thanks.
You might need to have this code in your user.rb
delegate :can?, :cannot?, to: :ability
def ability
Ability.new(self)
end
instead of load_and_authorize_resource, you can just use authorize_resource and then check. I don't think we need delegate here because the CanCanCan will do that automatically. It will automatically add these methods to User model.

Block redirect to other posts pages

I have 2 tables: posts and users(their relation is many-to-many), User has many favorite_posts(with FavoritePost table(it consists of user_id and post_id).
So, i have a route:
get 'favorite_posts', to: 'favorite_posts#index'
(users/:user_id/favorite_posts)
In my ability.rb:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.new_record?
can :read, [Post]
else
can :read, [Post]
can :manage, [Post], owner_id: user.id
can :manage, [FavoritePost], user_id: user.id
end
end
end
In my controller(favorite_posts_controller.rb):
class FavoritePostsController < ApplicationController
load_and_authorize_resource through: :current_user
def index
#favorite_posts = User.find(params[:user_id]).favorite_posts
end
So, i need to block redirect to pages with favorite posts of other user through ability.rb. What i need to do?
Take a look at this quote from a CanCan maintainer:
CanCan can answer the question whether the user can or can't do
something, but what the app does from there is very context specific
and I don't think is a good fit for the ability.rb file.
If you don't want to let other users view the current user's favorite posts, it's best to put this in a before_action filter in your favorite_posts_controller:
class FavoritePostsController < ApplicationController
load_and_authorize_resource through: :current_user
before_action :correct_user, only: [:index] # add any other actions you want
def index
#favorite_posts = current_user.favorite_posts.all
end
private
def correct_user
user = User.find(params[:user_id])
redirect_to root_url unless current_user && current_user == user
end
end

Devise and CanCan cannot :destroy User

I have an app that is using CacCan and Devise. I am having Devise handle the User destroy action
The route
DELETE /users(.:format) devise/registrations#destroy
My Controller
class UsersController < ApplicationController
skip_before_filter :authenticate_user!, :only => :portfolio
def index
redirect_to dashboard_user_path(current_user)
end
def dashboard
...
end
def portfolio
end
end
My ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.role?('Administrator')
can :access, :rails_admin
can :dashboard
can :manage, :all
else
cannot :destroy, User
can :read, :all
...
end
end
end
This code above does not work. A user who is not an administrator still has the ability to delete a user. I am assuming the reason is that I do not have UsersController#destroy method.
So my question is, How do I make CanCan prevent a user who is not an administrator from being able to delete a user?
Any help would be greatly appreciated.
Thanks
It seems to me that you defined abilities but not use them at all.
I suggest to read at least this (gem has a reach wiki with very useful information so it worth to read all articles).
I prefer to use powerful load_and_authorize_resource but in you case maybe enough authorize! (code is NOT tested!)
def destroy
#user = User.find(params[:id])
authorize! :destroy, #user
...
end

best way to establish a relationship between 3 models?

I have these 3 models: and here is what i need:
Basically I have records, and a user must hold a specific role assigned to him to be able to make changes to that record. each different record can have multiple roles and each role can belong to many different versions of a record. now that im having a version control, each single role can belong to many different versions of a record( different versions of the one record can be associated through the common secondary_id attribute for records).
What is the best way to establish this relationship between the three models?
thanks
Use cancan, http://github.com/ryanb/cancan. It'll give you exactly what you need.
Try this:
Change your User model:
class User
has_many :roles
has_many :records
def has_access_to?(obj)
roles.exists?(:id => obj.role_ids)
end
end
Controller code:
class RecordsController < ApplicationController
before_filter :require_user # checks if the user is logged in
before_filter :load_record, :except => [:index]
before_filter :require_permission, :except => [:new, :create, :index]
private
def require_permission
return true if current_user.has_access_to?(#record)
render :text => "You don't have permission to complete this action.",
:status => '401 Unauthorized'
return false
end
def load_record
case(action.to_sym)
when :new, :create
#record = current_user.records.build(params[:record])
when :edit, :update, :show, :destroy
#record = current_user.records.find(params[:id])
end
end
end

before_filter :require_owner

I have a number of resources (Trips, Schedules, etc) with actions that should be limited to just the resource's owner.
How do you implement code with a #require_owner method defined in ApplicationController to achieve this? Ideally, the code will look up the inheritance chain for the owner so the before_filter will work on a :comment that belongs_to :trip that belongs_to :user.
class TripsController < ApplicationController
belongs_to :member
before_filter :require_owner
...
end
I don't fully follow the description (would a comment really be owned by the trip owner?), but expanding slightly on jonnii's answer, here is an example that restricts the trip controller:
class ApplicationController < ActionController::Base
...
protected
# relies on the presence of an instance variable named after the controller
def require_owner
object = instance_variable_get("##{self.controller_name.singularize}")
unless current_user && object.is_owned_by?(current_user)
resond_to do |format|
format.html { render :text => "Not Allowed", :status => :forbidden }
end
end
end
end
class TripsController < ApplicationController
before_filter :login_required # using restful_authentication, for example
# only require these filters for actions that act on single resources
before_filter :get_trip, :only => [:show, :edit, :update, :destroy]
before_filter :require_owner, :only => [:show, :edit, :update, :destroy]
...
protected
def get_trip
#trip = Trip.find(params[:id])
end
end
Assuming the model looks like this:
class Trip < ActiveRecord::Base
belongs_to :owner, :class_name => 'User'
...
def is_owned_by?(agent)
self.owner == agent
# or, if you can safely assume the agent is always a User, you can
# avoid the additional user query:
# self.owner_id == agent.id
end
end
The login_required method (provided by or relying on an auth plugin like restful_authentication or authlogic) makes sure that the user is logged in and provides the user with a current_user method, get_trip sets the trip instance variable which is then checked in require_owner.
This same pattern can be adapted to just about any other resource, provided the model has implemented the is_owned_by? method. If you are trying to check it when the resource is a comment, then you'd be in the CommentsController:
class CommentsController < ApplicationController
before_filter :login_required # using restful_authentication, for example
before_filter :get_comment, :only => [:show, :edit, :update, :destroy]
before_filter :require_owner, :only => [:show, :edit, :update, :destroy]
...
protected
def get_comment
#comment = Comment.find(params[:id])
end
end
with a Comment model that looks like:
class Comment < ActiveRecord::Base
belongs_to :trip
# either
# delegate :is_owned_by?, :to => :trip
# or the long way:
def is_owned_by?(agent)
self.trip.is_owned_by?(agent)
end
end
Make sure to check the logs as you are doing this since association-dependent checks can balloon into a lot of queries if you aren't careful.
There's a few different ways to do this. You should definitely check out the acl9 plugin (https://github.com/be9/acl9/wiki/tutorial:-securing-a-controller).
If you decide you want to do this yourself, I'd suggest doing something like:
class Trip < ...
def owned_by?(user)
self.user == user
end
end
class Comment < ...
delegate :owned_by?, :to => :trip
end
# in your comment controller, for example
before_filter :find_comment
before_filter :require_owner
def require_owner
redirect_unless_owner_of(#commemt)
end
# in your application controller
def redirect_unless_owner_of(model)
redirect_to root_url unless model.owned_by?(current_user)
end
Forgive me if there are any syntax errors =) I hope this helps!
Acl9 is a authorization plugin. I'd give you the link, but I don't have cut and paste on my iPhone. If no one else provides the link by the time I get to a computer, I'll get it for you. Or you can google. Whichever. :)
I have only just started using it, but it has an extremely simple interface. You just have to create a roles table and a roles_user. Let me know how it goes if you decide to use it.
Or just use inherited resources:
InheritedResources also introduces another method called begin_of_association_chain. It’s mostly used when you want to create resources based on the #current_user and you have urls like “account/projects”. In such cases you have to do #current_user.projects.find or #current_user.projects.build in your actions.
You can deal with it just by doing:
class ProjectsController < InheritedResources::Base
protected
def begin_of_association_chain
#current_user
end
end

Resources