#386 Authorization from Scratch Part 2 pro
Oct 11, 2012 | 20 minutes | Security, Authorization
This finishes the series on building authorization from scratch by refactoring the permission logic into a DSL, restricting authorization with attributes, and combining with strong_parameters to protect params.
- Download:
- source code
- mp4
- m4v
- webm
- ogv
In regards to the use of
Array(controllers)
to enumerate over potentially singular things, I have been using another technique I saw at http://www.rubyinside.com/21-ruby-tricks-902.html #18:[*controllers].each...
Is there anything wrong with that form that I don't know about?+1
+1
Very nice! Especially liked that it was all done with testing.
Not sure that I like putting permissions definitions into separate files though. I would expect to see those directly in controllers and models defined with class method calls. The same way as attr_accessible is used - right from the model - for example. But maybe I just don't see all the benefits of using separate files yet, need to play with the code first I guess. Or maybe it's just a matter of taste :)
Anyway, great learning material!
Is there a way to whitelist for any actions within a controller using this method?
In the method you suggested in part 1, we did this:
A new-bee here. Can someone kindly explain what the 'resource' argument refers to. I am reading the code and I know what 'controller' and 'action' are referring to, but I am clueless about the 'resource' being passed as an argument stands for.
Many thanks.
A link to the code on railscast repo: http://bit.ly/TceGen
app/models/permissions/base_permission.rb
'resource' is the model that gets checked. Ryan's example is that you can only edit a topic if you created it. So, resource would be the Topic model.
Watch around the 5:00 mark.
Many thanks @Scott for clarifying that.
This is most mind-expanding episode I've watched so far. I can't even get myself to code today because I keep thinking of ways to implement this into my project. It's possible I could remove dozens of files and lots of complexity, while improving the testability with this method.
There's nothing like having the author of cancan explain authorization. Thank!
any ideas how to implement the accessible_by for the index action?
like with cancan:
@projects = Project.accessible_by(current_ability)
# This module adds the accessible_by class method to a model. It is included in the model adapters.
# An alternative action can optionally be passed as a second argument.
#
# @articles = Article.accessible_by(current_ability, :update)
#
# Here only the articles which the user can update are returned.
Permissions.permission_for(user) shows nearly what i'm looking for, but it doesn't work for the index action where a the customer_id matches.
still didn't found a way to do it, but the multi tenancy casts are a great help to succeed with my goals.
thanks so much to ryan for all the great episodes!
Can anyone point me in the right direction? I want to allow a user to only show, edit, update, or destroy their own user resource? I've implemented the @current_user caching from rails cast #274 Remember Me & Reset Password
I get caught in an infinite redirect loop when a normal user tries to show their own user profile. Any tips or suggestions would be appreciated. Mahalo
Figured out the answer to my problem....I forgot to override @current_resource
Hey thanks so much for sharing your code Spencer, it really helped me with customizing this episode to my project. You're awesome!
I was able to accomplish the same thing by having an @user = current_user for show, edit, update and destroy in the Users controller. However, your solution or mine, seems to remove the ideal of having all authorization located in one place given we have to adjust the Users controller to make things work there.
I'd guess the solution you came to with the do block in your member permission is the right direction. I'm going to play with that some.
How do you adapt when authorization depends on a parent model when the resource is not a singleton? Say
topics#index
can only be accessed if the forum is public. Thetopics#index
returns an array, not a singleton, so we can't calltopics.forum.private?
What would be really helpful is how to extend this to handle nested resources... Has anyone extended this to use nested resources and have some guidance to share?
Not sure if this is the most elegant, but this works if you want permission.rb working with Carrierwave.
References:
strong parameter example for hashes with integer keys
strong_parameters#nested-parameters
Fails if there is more than one nested resource. In my case I have: Timesheet-> accepts_nested_attributes_for :activities, Activity -> accepts_nested_attributes_for :time_entries. In permission file I declared it as follows:
Nevertheless, I always get the error that time_entries attributes is empty. Any idea ? Thanks and regards
How would you use this authorisation logic with Devise?
I tried:
But a guest is never able to login :(
Something like this worked for me:
Hope this helps
Hi,
Not work for me with rails 4 got a redirect loop :(
Any idea ?
Just found a solution.
I need to do this
I too would be very interested in seeing how to tackle authorization on an index page where the user should only see a list of appropriate objects.
Do you have plans to make another video or perhaps just show some example code for how this might be done?
I don't understand this line in the allow? method.
Could someone break it down for me please?
+1
from the line above the line you show,
allow
is eithertrue
or is ablock
or isnil
. The line you show then checks that (1)allowed
is notnil
and either (2a)allowed
istrue
or (2b)allowed
is a block which returnstrue
when passedresource
(andresource
must not benil
)Great explanation… very helpful! Thanks Justin. Also, I assume you meant allowed vs allow "is either true or is a block or is nil."
class Permission
def initialize(current_user)
allow :index_page, [:index]
allow :static_pages, [:about, :contact]
allow :sessions, [:new, :create, :destroy]
allow :users, [:edit, :update]
end
end
but there is something wrong
NoMethodError in PrimaryPagesController#index
undefined method `allow' for #Permission:0x007fcaee54a310
Rails.root: /Users/freshlhy/rails_projects/first_app
Application Trace | Framework Trace | Full Trace
app/models/permission.rb:3:in
initialize'
new'app/controllers/application_controller.rb:12:in
app/controllers/application_controller.rb:12:in
current_permission'
authorize'app/controllers/application_controller.rb:20:in
How is it possible to use that with nested attributes (forms). I have my models defined as follows:
Actually it does not work. As far I could see at Ryan's strong_parameters rails casts github https://github.com/railscasts/371-strong-parameters, he managed to do that (not very dynamically as he said and only for one post). In my case I have 1 or more activity with 7 time_entries each. Any idea how to do that? Thank you.
In the latest version of rspec, I'm getting errors like:
It seems the matcher is not valid anymore?
How would you rewrite it so that it works with the latest rspec?
Did you get anywhere with this?
Temp fix. while I'm trying to fix.
gem "rspec-rails", "2.13.2", :group => [:test, :development]
Bit of a cheat
i had same trouble in rspec-rails 2.14.0.
Because of rspec-mocks syntax changed. 'allow' matcher can't use.
Only rename
:allow
to:other_name
, then the code works well.+1
I renamed my
:allow
and:allow_params
matchers to:permit
and:permit_params
to get past this error.Here's my spec/support/matchers.rb file.
And my specs look like this (
should permit(...
instead ofshould allow(...
)I came here to share the same fix as given by others. I ended up renaming my authorize method to "check_authorization" and renamed my allow, allow? etc... methods to authorize, authorize? etc...
After implementing that authorization solution, many of my controllers specs started failing because they are not authorized to run without the proper permission. How can I fake permission in a controller test to fix this problem?
Depends on what your are trying to do in your controller tests, but here is what I did
I am having the same issue as Danilo, all of my controller tests fail unless "guest" can do that action. The controller scaffolding provides :valid_session which seems like it should help with this problem, but I can't figure out how to configure it. Anyone done this?
When I try Chris's method I get:
NoMethodError:
undefined method `stub' for #<MyController...
Any ideas on how to resolve this?
Using Rails 4.2.6, RSpec 3.4.0
While Ryan is on his Sabbatical, I think it important to keep some of this information up-to-date with the changes. Ryan developed a well-deserved reputation and his RailsCasts are referenced just about anywhere you can google a rails problem/solution.
As such, I want to comment on a couple of things regarding this episode.
The
strong_parameters
gem is automatically implemented in this episode. This is confusing because the docs for s_p shows controller-level checking, which has now become the standard for all Rails 4 apps.You may notice that there isn't a method
permitted_params
inside theapplication_controller
. All whitelisting logic has been moved to the application controller via theauthorize
method, which callscurrent_permission
. All strong_parameters checking is now handled bybase_permission.rb
inside thepermit_params!(params)
method and delegated to each of the permission classes - Admin, Member and Guest.I found it very advantageous to rename some of Ryan's code. In the video, he recommends renaming the
allow
method toallow_action
. DO THIS. Also, I took the liberty of renaming theallow_param
method toallow_attribute
, since that is what is being allowed and it removes identity confusion with the params hash.All-in-all, this is a great starting point for any Rails-based app. All you need to do is fork the code from GitHub, include the
rename
gem to change the name of the app, and start building your models. For each model you include, go back and whitelist the appropriate attributes (model fields) with the appropriate authorizations (admin, guest, etc.) Voila! Instant authorization including mass-assignment protection.Now, just pop on over to RC#250 (revised) and roll your own authentication. Both episodes merge without problems. Now you have a fully functional template for building just about anything.
Came here with a similar intent to Kraig.
The below gist tracks my changes to Ryan's code that make this project Rails 4.2 + RSpec 3.2 friendly.
https://gist.github.com/tennantje/1ce46a6eb3c66a949efd
How can we extends this solution to query ActiveRecord? Trying to keep the solution DRY.
So I would like to be able to use that logic to List(index) a resource and passing the Authorization parameters to ActiveRecord which would return only the authorized resources.
Cheers,
I came back to this solution while I was trying to teach myself code optimization and refactoring.
I tried as an exercise refactoring
class MemberPermission < BasePermission
toclass MemberPermission < GuestPermission
, seeing as how much of the static pages logic, etc. is being duplicated. I thought that is Members could inherit all the permissions of a guest, then it would be better.However, the filter chain halts as
:authorize
redirects in an infinite loop. Generally, this means that a permission hasn't been defined for that user and it's trying to redirect to static#home.I was wondering if anyone has any insight on this? I'll post to Stack as well..
The Stack question, and hopefully answer...
Thanks for this