ryanb / cancan Goto Github PK
View Code? Open in Web Editor NEWAuthorization Gem for Ruby on Rails.
License: MIT License
Authorization Gem for Ruby on Rails.
License: MIT License
Sometimes it would be convenient to use a "cannot" expression for defining abilities. For example.
can :read, :all cannot :read, [Order, Account]
The question is, should I allow blocks to be passed to this variation? At first I thought it would be confusing but I can think of some use cases now.
cannot :read, Product do |product| product.invisible? end
The load_resource
method should support finding database records by other columns than the id - eg. it should be possible to let it do MyModel.find_by_permalink!(params[:id])
.
Hello,
I have tried to load one active record model and many mongo mapper objects like this
load_and_authorize_resource :nested => [:contact, :buy, :criteria]
:contact -> activerecord
:buy, :criteria -> mongomapper
but only the :contact is loaded correctly, for the rest of the models I get instead of Buy object an Enumeration object.
Does anyone has played with cancan and mongo to give some hint?
I have
(admin) * controllers/admin/pages_controller.rb
(regular) * controllers/pages_controller.rb
I have a show function in both controllers, but the show function in /admin is only for admins to use.
with "can :show, Page" a regular user is granted access to both show functions. Can I limit that down to only the regular controller?
I got
def initialize(user) user ||= login_as_trial_user private def login_as_trial_user name = "anonymous_#{Time.now.to_i + rand}" if User.find_by_username(name) UserSession.create(User.find_by_username(name),true) else guest_role = User.create(:username => name, :password => name, :password_confirmation => name, :role => "guest", :email => "[email protected]") UserSession.create(guest_role, true) end @current_user_session = UserSession.find guest_role end
This is in the Ability.rb
Is that the correct way to go? Or should one use a before_filter in application_controller.rb so current_user is always set?
The problem is that SOMETIMES you don't need the current_user, and therefore it's uncessary to do a query for every request.
This way it only do it when the ability gets called.
:-) But I am trying to figure out wether this is the right approach?
I should add a bit of documentation about testing the CanCan ability model. One can use the can?
method on this directly to check the abilities.
ability = Ability.new(some_user) # assuming I change to initialize instead of prepare method assert ability.can?(:update, some_article)
Consider adding RSpec matchers for the can/cannot methods as well.
LOG:
Processing Admin::ComentariosController#index (for 127.0.0.1 at 2009-12-14 11:04:05) [GET]
Parameters: {"action"=>"index", "controller"=>"admin/comentarios"}
Admin::Config Load (0.4ms) SELECT * FROM admin_configs
LIMIT 1
NameError (uninitialized constant Comentario):
cancan (1.0.0) [v] lib/cancan/controller_resource.rb:10:in model_class' cancan (1.0.0) [v] lib/cancan/resource_authorization.rb:27:in
authorize_resource'
cancan (1.0.0) [v] lib/cancan/controller_additions.rb:103:in `authorize_resource'
Rendered rescues/_trace (42.5ms)
Rendered rescues/_request_and_response (1.2ms)
Rendering rescues/layout (internal_server_error)
CONTROLLER: Admin::ComentariosController
MODEL: Admin::Comentario
I guess issue is in resource_authorization.rb line 45..47:
def model_name
params[:controller].split('/').last.singularize
end
this do "admin/comentarios" became Comentario, but it isn't the real name "Admin::Comentario"
i don't have a patch yet, i'm just reporting.
If so, can you please post the link?
If I do something like
alias_action :home, :to => :read
it overwrites the previous aliases for :read
On lib/cancan/ability.rb:159
I think the arrays should be merged like:
aliased_actions[target] = args | (aliased_actions[target] ||= [])
Hi
i was trying to do something like this
can :manage, Comment do |comment| comment.try(:user) == user end
so, an user only can manage its own comments, but then in a view i use
- if can? :update, Comment = link_to t('edit'), edit_request_comment_path(comment.commentable, comment)(I'm using haml)
when i'm logged the link doesn't appear even is a current user's comment, so i'm a little confused about that...
and even worse, i can edit other user's comments inserting the corresponding URL in the browser without raise any exception
i'm using rails 2.3.5, i don't know what's happening here... i hope you can help me
greetings
(PS: excuse me if my english is not good enough, i hope that i made my point)
When I try to rake gems:install:
$ rake gems:install
(in /Users/tscolari/Projetos/skeleton)
rake aborted!
uninitialized constant ApplicationController::CanCan
(See full trace by running task with --trace)
I'm wondering why we cannot (or should not) pass in additional parameters to can?, eg
can? :manage, result, arbitrary_parameter1, arbitrary_parameter2
can :manage, Result do |action, result, arb_param1, arb_param2|
arb_param1 == 'blue' || arb_param2 == 42
end
This seems like a more flexible version of Accessing Request Data because the arbitrary parameters don't have to be included in the ApplicationController
code.
This dependancy should be mentioned somewhere in the documentation.
The resource should not be loaded if the instance variable is already set. This way one can override the resource loading on specific actions using before filters.
class BooksController < ApplicationController before_filter :find_book_by_permalink, :only => :show load_and_authorize_resource private def find_book_by_permalink @book = Book.find_by_permalink!(params[:id) end end
Here the resource will not be loaded for the show action since it has already been loaded. This could work for nesting too.
I'm trying to authorize a controller that does not have a model. When I try to load the controller "/calendar/patient_calendar", I get the error:
undefined method `cannot?' for CalendarController:Class
Here's my controller:
class CalendarController < ApplicationController
layout "users"
before_filter(:only => :patient_calendar) { unauthorized! if cannot? :read, :patient_calendar }
def patient_calendar
end
def public_calendar
end
end
And here are my abilities:
class Ability
include CanCan::Ability
def initialize(user)
if user.nil?
can :create, [Message, Gwapp]
can :read, :public_calendar
elsif user.user_type == "admin"
can :manage, :all
end
end
end
Any idea what's going on?
load_resource should try to intelligently load nested resources:
http://localhost:3000/foos/1/bars/3
should result in the functional equivalent of the following:
@foo = Foo.find(params[:foo_id])
@bar = @foo.bars.find(params[:id])
The can?
method is currently one of the most complex methods in CanCan. It could use some refactoring! Splitting it up into some private methods would probably be best.
Alternatively I could create a separate AbilityCheck object which would reduce the amount of parameter passing across methods, but I'm concerned about performance since the can?
method is triggered frequently in many applications.
I wonder if I can move some of the checking logic to up-front inside the can
call. I'll have to look into that as well.
class Admin::ItemColorsController < Admin::BaseController
before_filter :load_and_authorize_resource
... results in
NameError in Admin/item colorsController#index
`@admin/item_color' is not allowed as an instance variable name
I don't really know what I'm doing wrong, but I had this
if user.role?(:user)
can :create, Album
can :manage, Album do |album|
album.try(:user) == user
end
but the ability was returning an array so I got an "unknown method "user" for array" error. I debugged and notice that it was sending the action, and the object as elements... for example
can? :update, @album
was [:update, Album] and not just the album.
I had to add another variable to the block to make it work, so I ended up with something like this
if user.role?(:user)
can :create, Album
can :manage, Album do |act, album|
album.try(:user) == user
end
I don't really know why that happens and I was actually hoping you had an idea xD
Thanks,
This is an architecture or interface question, and possibly irrelevant, but I'm curious: is there a reason a standard Ability
class includes a CanCan::Ability
module instead of inheriting from a CanCan::Ability
parent class? To me the latter seems more natural. Is there another way CanCan can be used that is facilitated by the module design?
User Model
class User < ActiveRecord::Base ... def role?(role) roles.include? role.to_s end def roles=(roles) self.roles_mask = (roles & ROLES).map { |r| 2**ROLES.index(r) }.sum end def roles ROLES.reject { |r| ((roles_mask || 0) & 2**ROLES.index(r)).zero? } end def role_symbols roles.map(&:to_sym) end end
And Ability
class Ability ... if user.role? :administrator can :manage, :all cannot :manage, User do |u| u.role? :developer end end .. end
The error: undefined method `role?' for #Array:0xb6f01f0c
Currently this is a very common pattern in the controller.
unauthorized! if cannot? :update, @project
This could be more concise with an authorize
method.
authorize :update, @project
I'm on the fence with this one. It does make it cleaner but feels to go against the grain and simplicity of CanCan since it doesn't use the can?
method. I'm also unsure of the exact name of the method. Calling it authorize
would be ideal but it isn't consistent with unauthorized!
.
I would love to hear feedback. What is your opinion?
I just got cancan running in my cucumber features, and thought it would be helpful to include a few notes in either the wiki or the readme for those unused to rescuing from features.
Specifically, "rescue_from CanCan::AccessDenied {}" does not work in cucumber unless you allow rails to rescue from exceptions. It took me quite a while to figure out what the problem was, first of all, and that cucumber allows you to tag individual scenarios with @allow-rescue. Do you have a preferred way of integrating cancan into features?
Use Ability#initialize
instead of Ability#prepare
. This way it follows Ruby's convention better and simplifies the current_ability
method in the controller.
My original reason for not defaulting to initialize
is because I didn't want it to interfere with an existing initialize method which one may be using. For example, if someone wants to turn the Ability class into an ActiveRecord object which is stored in the database they should be able to.
However this would still be possible with initialize
. One would simply need to override the current_ability
method in the controller and setup the Ability class the way they want.
I have a calendar controller which builds and displays a calendar but there is no Calendar model. When I use "load_and_authorize_resource" on the controller it fails with a "uninitialized constant" error. I realize that I probably shouldn't use "load_and_authorize_resource" in this case but I don't really know how to get CanCan to secure the controller otherwise.
Am I missing something simple?
See: http://railscasts.com/episodes/192-authorization-with-cancan questions 42 & 44 for more detail and another viewpoint.
Any reason why you opted to go with !can? rather than a cannot? method?
def cannot?(*args)
!can?(*args)
end
??
This example is provided in the documentation for specifying multiple actions and classes.
can [:update, :destroy], [Article, Comment]
However it still needs to be implemented.
Hi, Ryan!
So beta is out and I'm looking forward to use it in my new project, but I do need some great authorization system like yours cancan is. Are you planning to make it compatible with new rails?
Thanks.
Ivan.
Do you think is a good idea to return status code 403 when something is not authorized? I think this should be specially important when using cancan for Web Services authorization. And in that case, what kind of response should be rendered to the Web Service (for example, on a .xml request)?
It appears that the :nested
option only works for nested resources that aren't shallow. This is obviously a difficult problem to solve. It would be cool if it loaded the parent resource that the nested one belongs to and then checked the authorization.
Is there any way that it is possible to authorize a controller that does not have a model pair?
Suppose I have the follow model:
class Post < ActiveRecord::Base
belongs_to :owner, :class_name => 'User'
end
And the following controller:
class PostsController < < ApplicationController
load_and_authorize_resource :nested => :user
.....
end
While this works, it'd be neat if CanCan could initialise an @owner variable (rather than @user).
I imagine that this could be achieved by changing CanCan such that load_and_authorize_resource :nested => :owner
achieves this, or perhaps adding an extra option to the hash, such as :renamings => { :user => :owner }
Is this reasonable / feasible? (I can try to write the patch).
It would be great if CanCan::ResourceAuthorization#authorize_resource respected a :message option so that custom messages can be set for whole controllers.
CanCan::ResourceAuthorization
def authorize_resource
if @controller.cannot?(params[:action].to_sym, resource.model_instance || resource.model_class)
@options[:message].present? ? @controller.unauthorized!(@options[:message]) : @controller.unauthorized!
end
end
end
Then in whichever controller you want to apply a custom message to:
load_and_authorize_resource :message => "Please log in to continue."
or even just:
authorize_resource :message => "You must log in first."
Hey guys,
IMHO cancan is lacking one important functionality: Right now there seems to be no way to use cancan for controllers without corresponding models.
Possible scenarios in which this feature is necessary:
This guy here: http://github.com/ryanb/cancan/issues/closed#issue/30
had the same question, but I couldnt find the "existing issue" he is talking about.
Any plans to implement this feature any time soon?
Is there a way to implement permissions not just on the user account level but also on user groups?
If you try to write following ability:
can :manage, Project do |action, project|
unless project
return false
end
# ...
end
ActiveSupport throws the following error:
Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.
I'm guess this is due to return in the block returning from the method in ActiveSupport since it's a Proc and not a lambda.
It would be great if return was supported inside of the can blocks.
I know that load_and_authorize_resource
calls your records for each restful action but I am having a hard time understanding what it really calls (without adding debugg to each action and check in the console)
Is there a way to let me define my own records and etc without having to do a bunch of before_filters myself?
Also, when using before_filter :this_method, :only => [:create, :update]
Do I need to define the other actions as well since I used a before filter myself?
Or does load_and_authorize_resource
continue with it's own filters?
It would be quite convenient to have the denied controller & action names in the exception, so that it would be
While the first two points seem to be also addressed by #33 (http://github.com/ryanb/cancan/issues/#issue/33) -- albeit not fully, the thrid one remains entirely on my wish-list.
I wish that 'can' sent something other than nil to the block when there is no instance variable. Example:
can :read, Article do |article| # If this is the :index action, article will be nil # but if it is the :show action, article will be @article instance article && article.user == user # or alternately article.try(:user) == user end
The problem I have is that the following block of code above returns unauthorized for the :index action because nil.user != user
So, I either have to split out the index/show actions (a little less DRY and more clutter) or do something like this:
article.nil? || article.user == user
That block of code will authorize both the index & show action. However, I'm uncomfortable authorizing article.nil? as that could exist in the show action and not be what I want (as there are many reasons, not always good, that the article instance variable could be nil).
I wonder if it would be better to pass a Class object to the block for the index action. Then the block check would be:
article.is_a?(Article) || article.user == user
Of course, you might still need to test for nil, but then your testing for an error condition not a valid condition.
I'm not set on my proposed solution, just uncomfortable with nil representing a valid authorized state.
Am I missing something, Ryan?
Create a separate example application repository (cancan_example) which is a simple Rails application to demonstrate basic functionality of CanCan.
This application should also be fully tested showing how one would test with CanCan.
Why there is no such method for user? I wrote a small mixin http://gist.github.com/305797. Please have a look at it. And now:
class User include CanCan::Authoritative end
What do you think?
why always shows
undefined local variable or method `current_user'
when i am defined ability and using authlogic
In my routes I have:
map.resources :articles # the public actions for articles map.namespace :admin do |admin| admin.resources :articles # the administrative pages for articles end
It appears that both of these pick up the abilities defined in:
can :read Article
I want to set different permissions for each of the resources. So, I may give a reviewer the right to view all articles in the public side, but may not want to let him view any articles in the admin section (perhaps I expose more sensitive data about the article in the admin section).
I tried obvious things like:
can :read, AdminArticle can :read, Admin::Article can :read, Admin/Article
All of those tries threw errors. Any idea how this might be able to be accomplished?
I'm not sure if it's an issue with rspec --pre or with cancan on 1.9.2. I figured I'd file it here, in case there's a workaround or fixes that can help the gems interoperate. When running my specs I get an unexpected nil object error.
This works in rails 2.3.5, rspec-rails 1.3.2, ruby 1.8.7-p249
1) User can update itself
Failure/Error: a = Ability.new(u)
You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.[]
# /Users/sax/.rvm/gems/ruby-1.9.2-head/gems/activesupport-3.0.0.beta/lib/active_support/core_ext/module/introspection.rb:9:in `parent_name'
# /Users/sax/.rvm/gems/ruby-1.9.2-head/gems/activesupport-3.0.0.beta/lib/active_support/core_ext/module/introspection.rb:31:in `parent'
# /Users/sax/.rvm/gems/ruby-1.9.2-head/gems/activesupport-3.0.0.beta/lib/active_support/dependencies.rb:102:in `const_missing'
# /Users/sax/Projects/personal/dblf/spec/models/user_spec.rb:6:in `block (2 levels) in <top (required)>'
# ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.role? :admin
can :manage, :all
else
can :manage, User do |action, u|
action != :destroy && u == user
end
end
end
end
# user_spec.rb
require 'spec_helper'
describe User do
it "can update itself" do
u = Factory(:user)
puts u
a = Ability.new(u)
a.can?(:update, u).should be_true
end
end
There are times one needs to set permission on something which doesn't have to do with a model or ruby class. Perhaps there's a page which displays general statistics about the site. One should be able to use a symbol or any other object when defining abilities, and then it will only match that same object.
can :read, :stats can? :read, :stats # => true
If one passes a block to this, then no additional objects should be passed since we aren't dealign with a class here.
can :read, :stats do # ... end
So in this case the block is a little pointless since you can define conditions in an "if" condition outside of this. The only real difference is the block gets executed on each check.
The :stats
symbol could be any object, so in theory this could be used to match a specific record in the database. However, I recommend using the block for performance reasons.
can :manage, user.profile # don't do this can(:manage, Profile) { |profile| user.profile == profile } # do this
This will lead to better performance since you are only loading the record object when performing the check and not on every request.
This should be mentioned in the documentation.
In an application where it is possible to delete resources, one user may destroy an item while a second user has a link to that resource still up on the browser. When the second user clicks the link, load_and_authorize_resource will cause an exception when trying to create the page. Typical message is "Couldn't find Item with ID=47".
Normally, I'd just intercept the exception and apologize/redirect. But I am thinking that it would be cool if load_and_authorize_resource handled the "not found" case in a more friendly way since this situation can occur naturally, without any hacking or really anything resembling a genuine error, etc.
The "CanCan::AccessDenied" exception type makes handling that case easy enough, so perhaps throwing a specific exception type ("ResourceAuthorization::NotFound" ??) from ResourceAuthorization load_resource would be helpful. That would make it easy to handle the exception in an appropriate way at the application level, or by controller.
Any opinions on this? Am I just being a weenie and should just handle this with a general application-level rescue?
Hi Ryan,
I was trying to use CanCan with Inherited Resources but I got to a problem when using the "authorize_resource" alone, without "load_resource".
This method ends up counting on an instance variable @model but that doesn't get set when using Inherited Resources until you call the "resource" helper (lazy loading). The way to make it work would be fetching it from the "resource" helper.
What do you think of adding a param/option to "authorize_resource" which would allow determining an alternative strategy of fetching the resource?
Thanks,
Marcelo Silveira
So the developer can give more detailed messages to the user, instead of "You are unable to access this page."
in rails 2.3.5 controller with a custom controller option in the routes, the controller has the format
:controller => "NameSpace::MyController"
instead of :controller => "name_space/my_controller"
in routes
map.resource "name", :controller => "NameSpace::MyController"
this raises an exception in controller_resource in model_instance, because of resource_authorization on line 51 ...
NameError: `@NameSpace::MyController' is not allowed as an instance variable name in ancan-1.0.2/lib/cancan/controller_resource.rb:27
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.