Rails4: Restrict Controller Destroy Action - ruby-on-rails

I set up my current_user in the Controller so User's Cant Edit other User's Entries.
But i can get it working for the Destroy Action.
My only solution is to check if the User is the Actual Uploader and only then show him the Destroy link.
<% if current_user == shop.user %>
<td><%= link_to 'Destroy', shop, method: :delete, data: { confirm: 'Are you sure?' } %></td>
<% end %>
But isn't his a Bad Practice as someone can easily hack this ?
If someone could enlighten me on this...
Thank You :)

You can use the CanCan gem. Once installed, CanCan will generate an 'Ability' file where you can define what users can or cannot do. in you case for example, you might have
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.admin?
can :manage, :all
else
can :read, :all
end
end
Then in your views, you can do a simple check to see whether or not a user has the ability to modify that object. For instance:
<% if can? :destroy, shop %>
<td><%= link_to 'Destroy', shop, method: :delete, data: { confirm: 'Are you sure?' } %></td>
<% end %>
In your controller you can authorize actions like so:
class ShopsController < ActionController::Base
load_and_authorize_resource
def destroy
....
end
end
The "load_and_authorize_resource" line will automatically add authorization into each RESTful action. It would add this to your destroy action, for instance:
#shop = Shop.find(params[:id])
authorize! :destroy, #shop
It's a super useful gem and very well documented

Another approach:
Instead of passing a shop id to edit, update and destroy, you could use a singular resource(http://guides.rubyonrails.org/routing.html#singular-resources) instead of a plural.
routes.rb:
resources :shops, only: :show, :index
resource :shop, only: [:new, :create, edit, :update, :destroy]
You will also need to change the before_action so it fetches the shop of the current user instead of querying by the id passed as parameter:
def set_shop
#shop = current_user.shop
end
Because you are no longer fetching the shop to be destroyed/edited using an id provided by the web user, there is no way for him to make a custom request with a fake id.

You should hide it on the view, but you should also have control of it on your controller.
In your controller you must do something like a before_action (read about it here: http://guides.rubyonrails.org/action_controller_overview.html)

Related

how can destroy all task linked to login user in rails

I am trying to delete all task that is linked to logged in user but when I click on delete all button it shows the error
No route matches [POST] "/tasks/destroy_all"
task_controller.rb
class TaskController < ApplicationController
def all_destory
#user = current_user
#user.tasks.destroy_all
redirect_to user_tasks_path
end
end
route.rb
get '/tasks/destroy_all', to: 'task#all_destory', as: :destroy_all
HTML
<% #tasks.each do |task| %>
<%= task.daily_task %>
<%= task.date %>
<% end%>
<%= button_to "delete all", destroy_all_path %>
When destroying records you want to use the DELETE HTTP verb.
GET requests are saved in the browsers history and should not create, modify or destroy anything on the server.
Typically in Rails you just have a route to destroy a single record. But if DELETE /things/1 deltes a single resource then DELETE /things should logically destroy the entire collection:
get '/user/tasks', to: 'users/tasks#index', as: :user_tasks
delete '/user/tasks', to: 'users/tasks#destroy_all'
# app/controllers/users/tasks_controller.rb
module Users
class TasksController < ApplicationRecord
before_action :authenticate_user!
# display all the tasks belonging to the currently signed in user
# GET /user/tasks
def index
#tasks = current_user.tasks
end
# destroy all the tasks belonging to the currently signed in user
# DELETE /user/tasks
def destroy_all
#tasks = current_user.tasks
#tasks.destroy_all
redirect_to action: :index
end
private
# You don't need this if your using Devise
def authenticate_user!
unless current_user
redirect_to '/path/to/your/login',
notice: 'Please sign in before continuing'
end
end
end
end
<%= button_to "Delete all", user_tasks_path, method: :delete %>
Your HTTP verb and your route must match. Currently your button is using POST, but your route accepts GET. You could change them both to POST.
post '/tasks/destroy_all', to: 'task#all_destory', as: :destroy_all
This fixes the problem in the question, but it's not ideal. As #max points out, DELETE would be more communicative of what clicking the button does– delete resources.
DELETE documentation

Best approach to hide certain user information on rails using devise as the method for authentication?

Currently I'm creating a rails app for understanding the framework right now my controller authenticates the user, I have three types of user using enum and a field called row in the users table and on my application controller I have this
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
protected
def authenticate_editor!
redirect_to request.referrer, :flash => { :error => "You don't have
the permissions to do this!" } unless user_signed_in? &&
current_user.editor?
end
def authenticate_admin!
redirect_to request.referrer, :flash => { :error => "You don't have
the permissions to do this!" } unless user_signed_in? &&
current_user.admin?
end
end
So right now on my controllers, I defined a before action for those protected methods, for example the admin role is the only one who can't destroy posts in my posts_controller. That works but on my view any user can see the destroy action link on the index page of that model, if a regular user clicks that link, the flash appears and he's unable to complete the action but I don't want regular users to see the link if is useless to them, I know I can hide it for example using this:
<% if current_user.admin? %>
<%= link_to 'Destroy', post_path(post),
method: :delete,
data: { confirm: 'Are you sure?' } %>
<% end %>
But I want to know if this is the correct approach, should I continue using conditional for those links to actions or if there's a better way?

Making a Moderator Authorized User role for my app

I have 2 types of roles for my user at the moment [:member , :admin], members can CRUD most post created by them . :admin can do CRUD any post period. Now im trying to create a moderator that can only View and update all posts. i have added :moderator to my enum role:. I also included
before_action :moderator_user, except: [:index, :show] and
def authorize_user
unless current_user.admin?
flash[:alert] = "You must be an admin to do that."
redirect_to topics_path
end
end
def moderator_user
unless current_user.moderator?
flash[:alert] = "You must be an moderator to do that."
redirect_to topics_path
end
end
but seem to be interfering with my before_action :authorize_user, except: [:index, :show] because it causes my rspec tests to fail.
Im trying to figure out how to create a moderator role which will be in between member and admin but without affecting either.
helper.rb :
def user_is_authorized_for_topics?
current_user && current_user.admin?
end
def user_is_moderator_for_topics?
current_user && current_user.moderator?
end
This is a perfect case for one of the authorization gems -- Pundit or CanCanCan. CanCanCan is probably the best for user-centric implementations...
#Gemfile
gem 'cancancan', '~> 1.13'
#app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in) #-> looks for "current_user"
case true
when user.admin?
can :manage, Post #-> CRUD all
when user.moderator?
can [:read, :update], Post #-> update/read posts
when user.member?
can :manage, Post, user_id: user.id #-> CRUD their posts
end
end
end
The above will give you the ability to use the can? and authorize methods in your controller & views:
#app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
load_and_authorize_resource
end
#app/views/articles/index.html.erb
<% #articles.each do |article| %>
<% if can? :update, article %>
<%= link_to "Edit", edit_article_path(article) %>
<% end %>
<% end %>
The above should do well for you.
The load_and_authorize_resource filter should provide you with scoped data:
As of 1.4 the index action will load the collection resource using accessible_by.
def index
# #products automatically set to Product.accessible_by(current_ability)
end
--
There is a great Railscast about this here. The creator of Railscasts authored CanCan before getting burned out, so a new community took it up with CanCanCan.

How to restrict edit action to only current user

How can you make a user edit action only available if the user is current user? I am using devise.
Devise has this:
before_action :authenticate_user!, only: [:new, :edit, :update, :destroy], notice: 'you must sign in first!'
But all this does is make sure a user is logged in not if a user is equal to current user? I want to make sure other users aren't able to edit other users accounts.
What is the best way to do this? Should I create a new before_filter? I couldn't find any standard way.
You can use the current_user method provided by devise. Here you can read more -current_user method.
def edit
unless current_user
redirect_to home_path, :alert => "Restricted area"
end
end
I highly advise looking into the CanCanCan gem to handle these things. In such a case your code would look something like:
View:
<% if user_signed_in? %>
<% if can? :update, #user %>
# Edit something
<%= link_to edit_profile_path(#user), class: 'user' do %>
Edit your profile
<% end %>
<% end %>
<% end %>
And in your Users controller or such you would add the following line which would take care of the case where a user manually types a url unto the browser:
Controller:
class UsersController < ApplicationController
load_and_authorize_resource
...
More info and docs: https://github.com/CanCanCommunity/cancancan

ActiveAdmin actions

is there a way to specify in ActiveAdmin's index page of a model what actions are allowed, things like:
index do
actions :edit
end
index do
actions only: :edit
end
do not work. What's the correct syntax?
Appreciated.
bundle show activeadmin
/home/muichkine/.rvm/gems/ruby-2.1.2/bundler/gems/active_admin-9cfc45330e5a
Add whatever actions you want to be available by using actions (it is usually put under model definition):
ActiveAdmin.register YourModel do
actions :index, :show, :create, :edit, :update
If you want to specify the method for certain action, you can do
action_item only: :show do
link_to 'Edit', action: :edit # so link will only be available on show action
end
Example how to play with the action column. In this example I just re-implemented the default one, but you can do powerful coding here:
column :actions do |item|
links = []
links << link_to('Show', item_path(item))
links << link_to('Edit', edit_item_path(item))
links << link_to('Delete', item_path(item), method: :delete, confirm: 'Are you sure?')
links.join(' ').html_safe
end
Do this way,
ActiveAdmin.register Foobar do
actions :all, :except => [:destroy]
end
or
ActiveAdmin.register Foobar do
actions :only => :edit
end
Need to be specified at resource level not in method definition
According to source code, https://github.com/activeadmin/activeadmin/blob/master/lib/active_admin/views/index_as_table.rb#L80
if one want to change the actions in the index he should go with
actions defaults: false do |sample|
link_to t('active_admin.edit'), admin_sample_path(sample)
end
where you can replace the link title and the path for the action
For Example:
actions defaults: false do |user|
link_to t('active_admin.view'), admin_user_path(user)
end
Note:
Keep in mind that add the path correctly like for show it should be admin_user_path(:id) and for index it should be admin_users_path :)

Resources