CanCanCan Authorize creation of a child record based on its parent's attributes - cancancan

I'm currently trying to set up a CanCanCan authorization on a child model based on a parent models attributes, but I can't seem to get it to work. I need to to something like the following:
can(:create, Bid) { |b| b.request.status == :open }
So in my situation, a Request can have many bids, but you should only be able to create bids when the request is still open for bidding.
The problem is the instance of a Bid will always have a request of nil.
I noticed authorization runs before the controller does, so it uses my bid_params before I run #bid = #request.bids.build(bid_params) in the controller.
I have tried using load_and_authorize_resource :bid, through: :request but that doesn't seem to help.
What am I doing wrong?

I'd try to do it "manually" before using load_and_authorize_resource.
def create
#bid = #request.bid.find(params[:id])
authorize! :create, #bid
end
your ability can be rewritten in a simpler way as
can :create, Bid, request: { status: :open }
when this works, also using
load_and_authorize_resource :request
load_and_authorize_resource through: :request
should work and you can remove your "manual" call to authorize!

Related

Ruby on Rails CanCan Gem

I am a bit confused regarding CanCan Gem. I basically understand how to set up abillity.rb. For example lest say we have the following code:
// in abillity.rb
user ||= User.new
can [:update, :destroy, :edit, :read], Book do |book|
book.dashboard.user_id == user.id
end
And then lets say we have the following books controller:
// books_controller.rb
load_and_authorize_resource
def destroy
if can?(:destroy, #book)
#book.destroy!
redirect_to happy_world_path
else
redirect_to not_happy
end
end
My question is: Do we need to check 'can?(:destroy, #book)'?
From my understanding 'load_and_authorize_resource' will not even allow access to this method if we don't have abillity to destroy it.
Yo do not need to add if can?(:destroy, #book) in your action if you use load_and_authorize_resource
Like the README say
Setting this for every action can be tedious, therefore the load_and_authorize_resource method is provided to automatically authorize all actions in a RESTful style resource controller.
If an user without authorization try to destroy, he get a unauthorized response ( not remember if is a 401 code)
Maybe you can use if can?(:destroy, #book) in your views, to do no show thte destroy button. Like also in Check Abilities & Authorization section

How to authorize namespace, model-less controllers using CanCanCan?

What is the correct way to authorize and check abilities for a namespaced, model-less controller using CanCanCan?
After much googling and reading the wiki, I currently have
#controllers/namespaces/unattacheds_controller.rb
def Namespaces::UnattachedsController
authorize_resource class: false
def create
# does some stuff
end
end
#models/ability.rb
def admin
can [:create], :namespaces_unattacheds
end
#view/
<%= if can? :create, :namespaces_unattacheds %>
# show a create form to authorized users
<% end %>
This is not correctly authorizing the controller. Admins can see the conditional create form, but are not authorized to post to the create action.
post :create, valid_params
Failure/Error: { it { expect( flash ).to have_content "Successfully created" }
expected to find text "Successfully created"
got: "You are not authorized to access this page."
In one example, the wiki suggests creating a separate Ability class for a namespaced controller. https://github.com/CanCanCommunity/cancancan/wiki/Admin-Namespace
Is there a simpler way to achieve this? This app uses many namespaced controllers, I don't really want to create an ability class for each one.
Is there correct syntax to refer to the namespaced controller in the Ability class?
can [:create], Namespaces::Unattacheds
can [:create], :namespaces_unattacheds
can [:create], namespaces/unattacheds
????
It sounds like you are setting permissions on the Namespaces::Unattacheds model, which means your controller doesn't need to do:
authorize_resource class: false
Your controller does have a model. Maybe it also inherits from ApplicationController? (That would be a logical thing to do.)
If you are trying to avoid affecting certain controller methods, use only/except clauses, as described here:
https://github.com/CanCanCommunity/cancancan/wiki/Authorizing-controller-actions#choosing-actions
I don't think the namespace depth is an issue IF it matches between your model and your controller. You just need load_and_authorize_resource and the proper form in ability.rb:
can [:create], Namespaces::Unattacheds
Maybe not the prettiest solution but I managed to achive this by adding
skip_authorization_check
before_action { raise CanCan::AccessDenied unless current_user.can?(params[:action].to_sym, ::namespaces_unattacheds) }
If you do it like this, you can pass whatever you want from this controller to the ability class.
You need to add the can? method first to be able to use this https://github.com/CanCanCommunity/cancancan/wiki/Ability-for-Other-Users

Why are `scope`-oriented actions (particularly `index` actions) treated differently in Pundit?

I am writing with respect to https://github.com/elabs/pundit#scopes
I am under the impression that authorization should answer the question Are you allowed access to this resource?, i.e. a true/false answer. This is the case with all actions except index, which, according to Pundit's docs, should return different ActiveRecord::Relation's depending on who is asking. For example, an admin gets scope.all, while a regular user gets scope.where(:published => true).
app/policies/post_policy.rb
class Scope < Struct.new(:user, :scope)
def resolve
if user.admin?
scope.all
else
scope.where(:published => true)
end
end
end
app/controllers/posts_controller.rb
def index
#posts = policy_scope(Post)
end
My reservation is that this is a slippery slope, and soon I will be adding presentation to the scopes (e.g. scope.all.order('created_at ASC')) -- and it just feels weird doing so in an authorization policy.
Of course I could move that to the controller...
def index
#post = policy_scope(post)
if user.admin?
#post = #post.order( 'created_at ASC' )
end
end
...but is that the controller's job? And I don't think it would be right to add such a call to the view. So maybe it should be a model method?
What would you say are the pros/cons of doing the following instead?
app/controllers/posts_controller.rb
This keeps index just like the other methods, with one call to authorize, and one call to a model method.
def index
authorize(Post)
#posts = Post.index(current_user)
end
app/policies/post_policy.rb
This simply gives a true/false answer. Are you authorized? Yes or no.
def index?
user.admin? || user.regular_user?
end
app/models/post.rb
And in the model we can get as fancy as we like.
def self.index(user)
if user.admin?
Post.all.order('created_at ASC')
else
Post.where(user_id: user.id)
end
end
Thoughts?
My understanding of authorization vs scopes in Pundit is as follows:
authorization: 'is this user allowed to act upon (create/update/destroy) this resource?'
within scope : 'should this user be able to see (index/show) this resource?'
Authorization (authorize #resource) defers to permitted_attributes in ResourcePolicy for the answer.
Scopes (policy_scope(Resource)) defer to resolve.
I believe the reasoning behind Pundit's scopes is that there should be only one location in your code where you define who should have access to what resources.
You could, as you've described, implement the same behavior in your controllers or your views. However, putting the code into a Policy guards against unauthorized access should you happen to forget to scope appropriately in one of your controller methods.
I think of policy_scope() as the way to restrict visibility, while other result refinements (e.g. sorting) can take place at the controller level. There's no doubt a lot of personal preference at play, however.

Rails scope find with current user

I'm using Rails 3 with Devise for user auth. Let's say I have a User model, with Devise enabled, and a Product model, and that a User has_many Products.
In my Products controller I'd like my find method to be scoped by current_user, ie.
#product = current_user.products.find(params[:id])
unless the user is an admin user, i.e. current_user.admin?
Right now, I'm running that code in almost every method, which seems messy:
if current_user.admin?
#product = Product.find(params[:id])
else
#product = current_user.products.find(params[:id])
end
Is there a more elegant/standard way of doing this?
I like to do this as follows:
class Product
scope :by_user, lambda { |user|
where(:owner_id => user.id) unless user.admin?
}
end
this allows you to write the following in your controller:
Product.by_user(current_user).find(params[:id])
If you're running this code in a lot of your controllers, you should probably make it a before filter, and define a method to do that in your ApplicationController:
before_filter :set_product, :except => [:destroy, :index]
def set_product
#product = current_user.admin? ? Product.find(params[:id]) : current_user.products.find(params[:id])
end
I don't know what you use to determine if a user is an admin or not (roles), but if you look into CanCan, it has an accessible_by scope that accepts an ability (an object that controls what users can and can't do) and returns records that user has access to based on permissions you write yourself. That is probably really what you want, but ripping out your permissions system and replacing it may or may not be feasible for you.
You could add a class method on Product with the user sent as an argument.
class Product < ActiveRecord::Base
...
def self.for_user(user)
user.admin? ? where({}) : where(:owner_id => user.id)
end
Then you can call it like this:
Product.for_user(current_user).find(params[:id])
PS: There's probably a better way to do the where({}).

How to test a named_scope that references a class attribute with Shoulda?

I have the following ActiveRecord classes:
class User < ActiveRecord::Base
cattr_accessor :current_user
has_many :batch_records
end
class BatchRecord < ActiveRecord::Base
belongs_to :user
named_scope :current_user, lambda {
{ :conditions => { :user_id => User.current_user && User.current_user.id } }
}
end
and I'm trying to test the named_scope :current_user using Shoulda but the following does not work.
class BatchRecordTest < ActiveSupport::TestCase
setup do
User.current_user = Factory(:user)
end
should_have_named_scope :current_user,
:conditions => { :assigned_to_id => User.current_user }
end
The reason it doesn't work is because the call to User.current_user in the should_have_named_scope method is being evaluated when the class is being defined and I'm change the value of current_user afterwards in the setup block when running the test.
Here is what I did come up with to test this named_scope:
class BatchRecordTest < ActiveSupport::TestCase
context "with User.current_user set" do
setup do
mock_user = flexmock('user', :id => 1)
flexmock(User).should_receive(:current_user).and_return(mock_user)
end
should_have_named_scope :current_user,
:conditions => { :assigned_to_id => 1 }
end
end
So how would you test this using Shoulda?
I think you are going about this the wrong way. Firstly, why do you need to use a named scope? Wont this just do?
class BatchRecord < ActiveRecord::Base
belongs_to :user
def current_user
self.user.class.current_user
end
end
In which case it would be trivial to test. BUT! WTF are you defining current_user as a class attribute? Now that Rails 2.2 is "threadsafe" what would happen if you were running your app in two seperate threads? One user would login, setting the current_user for ALL User instances. Now another user with admin privileges logs in and current_user is switched to their instance. When the first user goes to the next page he/she will have access to the other persons account with their admin privileges! Shock! Horror!
What I reccomend doing in this case is to either making a new controller method current_user which returns the current user's User instance. You can also go one step further and create a wrapper model like:
class CurrentUser
attr_reader :user, :session
def initialize(user, session)
#user, #session = user, session
end
def authenticated?
...
end
def method_missing(*args)
user.send(*args) if authenticated?
end
end
Oh, and by the way, now I look at your question again perhaps one of the reasons it isn't working is that the line User.current_user && User.current_user.id will return a boolean, rather than the Integer you want it to. EDIT I'm an idiot.
Named scope is really the absolutely wrong way of doing this. Named scope is meant to return collections, rather than individual records (which is another reason this fails). It is also making an unnecessary call the the DB resulting in a query that you don't need.
I just realized the answer is staring right at me. I should be working from the other side of the association which would be current_user.batch_records. Then I simply test the named_scope on the User model and everything is fine.
#Chris Lloyd - Regarding the thread safety issue, the current_user attribute is being set by a before_filter in my ApplicationController, so it is modified per request. I understand that there is still the potential for disaster if I chose to run in a multi-threaded environment (which is currently not the case). That solution I suppose would be another topic entirely.

Resources