Ruby on Rails: The same check in multiple actions in a controller - ruby-on-rails

Frstly my apologies if this is a duplicate question. I have tried to find the answer but as Im very new to Rails I did not know what to search for.
I have a controller that has some security on it. For the show, edit, update and destroy actions I need to check if the user owns the persona they are working on, like this:
if #persona.user_id != #current_user.id
flash[:notice] = "Sorry, we couldn't find that persona"
redirect_to '/personas/'
else
# do something else
This is relatively easy. However, how do I do this in a DRY way? The code before the else is repeated across all 4 actions, the code after the else statement will be different on a per controller basis.
Thanks in advance.
Richard

You will need to use a before_filter. Something like this:
class PersonasController < ApplicationController
before_filter :check_owner, :only => [:show, :edit, :update, :destroy]
def show
#...
end
#...etc.
protected
def check_owner
redirect_to personas_path unless params[:id] == current_user.id
end
end
Also, take #davidb's advice on writing a current_user method if you don't already have one, which will go in your application_controller.rb. Something like this:
class ApplicationController < ActionController::Base
helper_method :current_user
def current_user
#current_user ||= session[:user_id] ? User.find(session[:user_id]) : User.new
end
end
You may have to tweak all of this, since it will depend on how you have your models setup. This is just a general idea of what you need to/should do.

Use before_filter here is an overview:
http://guides.rubyonrails.org/action_controller_overview.html#filters
To dry it you should also write a current_user method that returns the logged in user!

You can move your security logic to the before_filter. It will run before your actions and make your security check.
Your controller file:
class TestController
before_filter :check_persona, :only => [:show, :edit, :update, :destroy]
private
def check_persona
if #persona.user_id != #current_user.id
flash[:notice] = "Sorry, we couldn't find that persona"
redirect_to '/personas/'
end
end
end

Related

What kind of logic should be in Rails before filter

Should I load and check the presence of the DB source in before filter?
In our application, we always load and check presence of DB source which has an id passed in by params. I'm not sure if this is a good pattern.
like:
before_action :set_org
private
def seg_org
#org ||= Organization.find params[:id]
resource_not_found unless #org
end
Yes, finding a record and setting it as an instance variable is a common convention for controller filters. Generally though, any piece of code that gets run for multiple actions is a good candidate. Say you want to redirect to the log in page if the current user is not logged in.
class UsersController < ApplicationController
before_action :require_login
before_action :set_user, only: [:show, :edit, :update, :destroy]
private
def require_login
unless logged_in?
flash[:error] = "You must be logged in to access this section"
redirect_to new_login_url # halts request cycle
end
end
def set_user
#user = User.find(params[:id])
end
end

How can I block url adress in Devise (Ruby on Rails)

I blocked display links on the show page:
<% if #post.user == current_user %>
links
<%end%>
but I can't block url adress for unprivileged users:
http://localhost:3000/posts/1/edit
What can I do?
It's good possibility to use Pundit gem (https://github.com/elabs/pundit).
Your policy will look:
class PostPolicy
attr_reader :user, :post
def initialize(user, post)
#user = user
#post = post
end
def edit?
post.user == user
end
end
And your controller's action:
def edit
#post = Post.find_by(id: params[:id])
authorize #post
...
end
What you're looking for is something called authorization
Authentication = finding out if a user is present
Authorization =
determining if they are able to perform specific requests
The answer by Sergei Stralenia is correct - you'll need to use one of the authorization gems -- Pundit and CanCanCan being two of the most popular -- to validate whether a user is able to edit a particular object.
In regard the routing, you'll not be able to remove the edit route, unless you separate it out into something like an admin namespace (I'll explain more in a second).
--
Sergei Stralenia's post showed you how to use Pundit, I'll show you CanCanCan:
#app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.admin?
can :manage, Post
else
can :read, Post
end
end
end
#app/controllers/posts_controller.rb
class PostsController < ApplicationController
def edit
#article = Post.find params[:id]
authorize! :edit, #article
end
end
Admin
If you wanted to make a post only editable in an "admin" area, you'd be best using something like the following:
#config/routes.rb
resources :posts, only: [:index, :show]
namespace :admin do
resources :posts, only: [:new, :create, :edit, :update, :destroy]
end
This way, you will literally have no way for a non-admin user to edit/update posts in the front-end. Instead, they'll have to go into the admin area and make it so that they are able to edit it in there...
#app/controllers/admin/posts_controller.rb
class Admin::PostsController < ApplicationController
#actions & authorization in here
end
Within the edit action on your controller, perform the same check - something like:
#post = Post.find_by( id: params[:id] )
unless #post.user == current_user
fail(ActionController::RoutingError, 'User cannot edit this post.')
end
You can simplify the error check into:
fail(ActionController::RoutingError, 'User cannot edit this post.') unless #post.user == current_user
I hope this helps!
I guess the best way to do this is to use before_filter in your posts controller, i.e.:
before_action :authorize_admin, only: [:show, :edit, :update, :destroy]
or:
before_filter :authorize_admin, except: [:show]
where :authorize_admin is the method that You have to define either in posts controller (to use for posts only) or in application controller (to use in all controllers), like this:
def authorize_admin
redirect_to :new_user_session unless current_user&&current_user.admin?
end

before_action logs out user post update (using devise)

I'm trying to restrict access to the user edit page so that logged in user can only edit his/her own profile. FYI, I'm using Devise for user authentication, login, register, etc. This is supposed to be pretty easy to do with
class UsersController < ApplicationController
before_action :find_user, only: [:edit, :update]
before_action :correct_user, only: [:edit, :update]
def edit
end
def update
if #user.update_attributes(user_params)
flash[:success] = 'Profile Updated!'
redirect_to edit_user_path(#user)
else
render 'edit'
end
end
private
def user_params
# code left out... but pretty self explanatory right?
end
def find_user
#user = User.find(params[:id])
end
def correct_user
redirect_to(root_url) unless current_user == find_user
end
end
The weird thing is, when I have the before_action :correct_user, when the user updates... it logs the user out post-update! When I don't have the before_action :correct_user, it leaves the user logged in and redirects to edit page of the user. I tried manually signing the user in def update prior to redirecting to edit page, but it does not work. In fact, that isn't even the issue. When I compare the current_user and User.find(params[:id]), the current_user is logged in! But for some reason, having the before_action :correct_user there logs me out!
I've been banging my head on the wall for quite some time on this one. Can anyone help out? This is a Rails 4 app and am using the latest version of devise.
Thank you!
I'm not sure you really need the find_user method here.
class UsersController < ApplicationController
respond_to :html
def update
current_user.update_attributes user_params
respond_with current_user, location: [:edit, current_user]
end
private
def user_params
...
end
end
Seeing as you only let a user edit their own record, you can just use current_user in the update method.
Also, if you are happy to use the standard Rails convention for CRUD operations, then respond_to/with will save you a little time and code by implementing that for you. I used the location option otherwise respond_with defaults to the show page of the resource.

Rails before_filter for specific actions in controller

def new
before_filter do
redirect_to "/" unless current_admin || current_company
flash[:notice] = 'You dont have enough permissions to be here' unless current_admin || current_company
end
CODE CODE CODE
end
def edit
before_filter do
redirect_to "/" unless current_admin.id = 5
flash[:notice] = 'You dont have enough permissions to be here' unless current_admin || current_company
end
CODE CODE CODE
end
This is the code that I want to do, but I cant figure out how to do it right.
What I want to achieve is to apply a before_filter rule for each of my actions. So perhaps a User can acces de INDEX action but not the EDIT action etc.
I know that the before_filter method runs a single time, and I cannot run 4 before_filters, I'm just giving some reference because of my poor english.
You must know that I am using Devise for the current_admin and current_company methods.
I need to apply different filters (if admin or if company.id = X) and other actions.
Thanks in advance, I am pretty stucked in here.
Any help will be appreciated.
Create in your ApplicationController method:
def check_privileges!
redirect_to "/", notice: 'You dont have enough permissions to be here' unless current_admin || current_company
end
And then in your controller:
before_filter :check_privileges!, only: [:new, :create, :edit, :save]
Or
before_filter :check_privileges!, except: [:index, :show]

How to obtain action level protection using Authlogic and STI?

Given that it is well-documented how to use before_filter for a single user classification, I'm having trouble getting action-level protection for multiple user types. Let me explain:
I've got something like this...
class ApplicationController < ActionController::Base
class << self
attr_accessor :standard_actions
end
#standard_actions = [:index, :show, :new, :edit, :create, :update, :destroy]
def require_guardian
unless current_user and current_user.is_a?(Guardian)
store_location
redirect_to home_url
return false
end
end
def require_admin
unless current_user and current_user.is_a?(Administrator)
store_location
redirect_to register_url
return false
end
end
end
And in the GuardiansController I want to only allow the standard actions for Administrator but all other actions should require Guardian. So I tried this...
class GuardiansController < ApplicationController
before_filter :require_admin, :only => ApplicationController::standard_actions
before_filter :require_guardian, :except => ApplicationController::standard_actions
...
end
Which ends up doing a recursive redirection. There must be a better way?
OK, this is another case of not looking carefully and missing something. I inadvertently had setup the route to redirect the user in a recursive way. The above solution works just fine when you set the routes properly:
def require_guardian
unless current_user and current_user.is_a?(Guardian)
store_location
# this route (home_url) sent the user to another controller which ran a before_filter sending them back here again.
# redirect_to home_url
# So I changed it to a neutral route and it works great!
redirect_to register_url
return false
end
end

Resources