I have several controllers that require a correct user for their edit/update/delete actions. What is the Rails-way to accomplish the following:
Currently, in each controller I have the following code:
class FooController < ApplicationController
before_filter :correct_user, :only => [:edit, :update, :destroy]
# normal controller code
private
def correct_user
#foo = Foo.find params[:id]
redirect_to some_path unless current_user == #foo.user
end
end
I have similar code in 3 controllers. I started to bring it out to a helper like this:
module ApplicationHelper
def correct_user( object, path )
if object.respond_to? :user
redirect_to path unless object.user == current_user
end
end
But I'm wondering if this is a good way to do it. What's the accepted way to solve this?
Thank you
EDIT
The correct user check here is because I want to make sure it's only the author who can make edits/deltes to each of the objects.
To clarify, the objects would be things like Questions and Posts. I don't want to use something like CanCan as it's overkill for something simple like this.
I really like using RyanB's CanCan, which allows you to both restrict access to actions based on the user, and centralize such authorization into basically a single file.
CanCan on GitHub: https://github.com/ryanb/cancan
Screencast explaining how to setup/use it: http://railscasts.com/episodes/192-authorization-with-cancan
EDIT
No problem. I hear you on CanCan - it takes a little while to get up and running on it, but it's designed to do exactly what you're asking - per object authorization.
Alternative:
Another way to do this is move your authoriship/current_user check to the ApplicationController class, from which all of your other Controllers inherit (so they will get that code through inheritance - and you don't need to write the same code in multiple Controllers), and it would look something like...
class ApplicationController < ActionController::Base
...
helper_method :correct_user
private
def correct_user( object, path )
redirect_to path unless object.user == current_user
end
end
You should do the following :
def edit
#foo = current_user.foos.find(params[:id])
end
This way, only if the current user is the owner of the Foo he will be able to see it.
Related
I was attempting to start a test to confirm that a user can only modify an object if current_user.id and model.user_id match.
I feel like this is a validation from the model. So I might write something like:
class UserLocked < ActiveModel::Validator
def validate(record)
unless record.user_id == current_user.id
record.errors[:name] << "Sorry you cannot modify something that is not your's"
end
end
end
Which might be ok... (is there a centralized place I can put this? do I need to do anything special to reference it then?)
Writing a test for that isn't too bad either; however, I also need to prevent the controller from displaying the form to edit form. Should I be creating a separate view or just make it part of the edit page? How can I write a test for this for this in rspec...
I might be over thinking this, but I am trying to figure out what everyone else is doing. An example would be great! I've done this before in other languages/frameworks, but I am trying to "do things the right way."
Thanks!
Authorization belongs in the controller and not in the model. So you could implement a before_filter like this:
class UsersController < ApplicationController
before_filter :correct_user, only: [:edit, :update]
...
private
def correct_user
#user = User.find(params[:id])
redirect_to root_path unless current_user? #user
end
end
Of course you would need some sort a method to detect who the current user is.
You could test this with a request spec, using RSpec & Capybara. The logic is simple: you login with a user and expect that when trying to edit the info of another user you get an error message displayed. Otherwise the relevant form fields should be displayed.
For an example see http://ruby.railstutorial.org/chapters/updating-showing-and-deleting-users#code:edit_update_wrong_user_tests
I working on an app with user authorization. It has a List and User classes. The authentication was built with Ryan Bates http://railscasts.com/episodes/270-authentication-in-rails-3-1
I'm not sure about authorization process. I read about cancan gem. But i could not understand.
I want to achieve this:
User only able to view/edit/delete his own list.
User only able to view/edit/delete his own profile(user class).
I don't implement user level right now. No guess or admin.
How to use before_filter method in list and User controller with current_user instance?
Since you are defining current_user in the application controller, this is easy. You can use before_filter like this in the Users controller:
class ItemsController < ApplicationController
before_filter :check_if_owner, :only => [:edit, :update, :show, :destroy]
def check_if_owner
unless current_user.admin? # check whether the user is admin, preferably by a method in the model
unless # check whether the current user is the owner of the item (or whether it is his account) like 'current_user.id == params[:id].to_i'
flash[:notice] = "You dont have permission to modify this item"
redirect_to # some path
return
end
end
end
###
end
You should add a similar method to UsersController to check if it is his profile, he is editing.
Also, have a look at Devise which is the recommended plugin for authentication purposes.
For this I'd not use devise. It's way to much for this simple use.
I'd make a seperate controller for the public views and always refere to current_user
Remember to make routes for the actions in the PublicController
class PublicController < ApplicationController
before_filter :login_required?
def list
#list = current_user.list
end
def user
#user = current_user
end
def user_delete
#user = current_user
# do your magic
end
def user_update
#user = current_user
# do your magic
end
# and so on...
end
I'm struggling a bit to find the right place for a helper method. The method basicly 'inspects' a User-model object and should return some information about the 'progress' of the user, eg. "You need to add pictures", "Fill out your address" or "Add your e-mail-adress". None of the conditions I'm checking for are required, it's just like a "This is your profile completeness"-functionality as seen on LinkedIn etc.
Each of these 'actions' have a URL, where the user can complete the action, eg. a URL to the page where they can upload a profile photo if that is missing.
Since I need access to my named routes helpers (eg. new_user_image_path) I'm having a hard time figuring out the Rails-way of structuring the code.
I'd like to return an object with a DSL like this:
class UserCompleteness
def initialize(user)
end
def actions
# Returns an array of actions to be completed
end
def percent
# Returns a 'profile completeness' percentage
end
end
And user it with something like: #completeness = user_completeness(current_user)
However, if I'm adding this to my application_helper I don't have access to my named routes helpers. Same goes if I add it to my User-model.
Where should I place this kind of helper method?
This is a similar problem to that of Mailers. They are models, and should not cross the MVC boundaries, but need to generate views. Try this:
class UserCompleteness
include ActionController::UrlWriter
def initialize(user)
end
def actions
# Returns an array of actions to be completed
new_user_image_path(user)
end
def percent
# Returns a 'profile completeness' percentage
end
end
But be aware you are breaking MVC encapsulation, which might make testing more difficult. If you can get away with some methods in the users helper instead of a class that might be better.
From the little i got your question i think you want a method which you can used in Controller as well as Views.
To Accomplish this simple add method in application_controller.rb and named it hepler_method
Example:-
class ApplicationController < ActionController::Base
helper_method :current_user
def current_user
#current_user ||= User.find_by_id(session[:user])
end
end
you can use method current_user in both Controller as well as views
I'm new to rails and don't even know if this is the correct way of solving my situation.
I have a "Club" ActiveRecords model which has a "has_many" association to a "Member" model. I want the logged in "Club" to only be able to administrate it's own "Member" so in the beginning of each action in the "Member" model I did something similar to the following:
def index
#members = Club.find(session[:club_id]).members
to access the right members. This did not however turn out very DRY as I did the same in every action. So I thought of using something equivalent to what would be called a constructor in other languages. The initialize method as I've understood it. This was however not working, this told me why, and proposed an alternative. The after_initialize.
def after_initialize
#club = Club.find(session[:club_id])
end
def index
#members = #club.members
....
does not seem to work anyway. Any pointers to why?
You have a nil object when you didn't expect it!
The error occurred while evaluating nil.members
Makes me think that the #club var isn't set at all.
Also, is this solution really a good one? This makes it hard to implement any kind of "super admin" who can manage the members in all of the clubs. Any ideas on where I am missing something?
You can use a before_filter.
Define the filter in your ApplicationController (so that you can access it from any controller).
class ApplicationController < ActionController::Base
# ..
protected
def load_members
#members = if session[:club_id]
Club.find(session[:club_id]).members
else
[]
end
end
end
Then, load the filter before any action where you need it.
For example
class ClubController < ApplicationController
before_filter :load_members, :only => %w( index )
def index
# here #members is set
end
end
Otherwise, use lazy loading. You can use the same load_members and call it whenever you need it.
class ClubController < ApplicationController
def index
# do something with members
load_members.each { ... }
end
end
Of course, you can customize load_member to raise an exception, redirect the client if #members.empty? or do whatever you want.
You want to use a before_filter for this.
class MembersController < ApplicationController
before_filter :find_club
def index
#members = #club.members
end
private
def find_club
#club = Club.find(session[:club_id])
end
end
I'm a fan of a plugin called Rolerequirement. It allows you to make custom roles and apply them by controller: http://code.google.com/p/rolerequirement/
I am trying to lock-down a few controllers based on role and the 'posts' controller by whether or not they ANY permissions assigned. This appears to be working, but I'm wondering if there is a clean way to handle this. This is what I have in the application controller, which I'm calling as a before filter...
if controller_name == 'users' || 'accounts'
unless #current_user.master? || #current_user.power?
render :template => "layouts/no_content"
end
elsif controller_name == 'posts'
unless #current_user.permissions.count > 0
render :template => "layouts/no_content"
end
end
Thanks in advance.
You shouldn't make a code snippet that checks for a controller name to take a specific action in application.rb. You should define that before filters only in the controllers that need them
Make 2 methods in ApplicationController:
private
def require_master_or_power_user
unless #current_user.master? || #current_user.power?
render :template => "layouts/no_content"
end
end
def require_some_permisions
unless #current_user.permissions.count > 0
render :template => "layouts/no_content"
end
end
Now add this as a before filter where you need it:
class UsersController < ApplicationController
before_filter :require_master_or_power_user
...
end
class AccountsController < ApplicationController
before_filter :require_master_or_power_user
...
end
class PostsController < ApplicationController
before_filter :require_some_permisions
...
end
So the ApplicationController defines the filters, but its up to your other controllers whether or not to actually use those filters. A superclass like the ApplicationController should never conditionally branch its execution based on its subclasses. Choosing when to use the provided behaviours are one of the reasons why you want to subclass in the first place.
It's also much clearer from a code readability standpoint. When looking at the UsersController, its immediately obvious there is some permission stuff happening when you see a before filter with the name like "require_something". With your approach, you can't tell that from looking at the users controller code itself at all.
I would strongly suggest you adhere to MVC and OOP and move as much of the user related logic back into the User model like this:
class User < ActiveRecord::Base
def has_permission?
true if self.master? || self.power? || (self.permissions.count > 1)
end
then you could just use one filter in application.rb:
protected
def check_template
render :template => "layouts/no_content" if current_user.has_permission? == true
end
and call that with a before_filter as suggested by Squeegy, either in the respective controllers, or site wide in application_controller.rb
before_filter :check_template
This approach is obviously a little cleaner and a lot less brittle if you ever decide to change the scope of what gives people permission, you only have to make one change application wide.
I would advise that you use an ACL system for this: http://github.com/ezmobius/acl_system2
A short little handwritten DSL. Haven't even checked the code for syntax errors, but you'll get the picture. In your application controller:
before_filter :handle_requirements
def self.requirement(*controllers, &block)
#_requirements ||= {}
#_requirements[controllers] = block
end
def handle_requirements
return unless #_requirements
#_requirements.each do |controllers, proc|
if controllers.include?(controller.controller_name)
restrict_access unless instance_eval(&block)
end
end
end
def restrict_access
render :template => "layouts/no_content"
end
Usage (also in your application controller)
requirement('users', 'accounts') do
#current_user.master? || #current_user.power?
end
Or, just use the ACL system Radar mentions.
Another plugin worth a look is role requirement, which I've been using. I think they can both do roughly the same things.
Here is a plug for RESTful_ACL; an ACL plugin/gem I've developed, and is being pretty widely used. It give you freedom to design your roles as you see fit, and it very transparent.