How to restrict edit action to only current user - ruby-on-rails

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

Related

Edit and update of user info. in RoR

I am using devise for user management so it let's user sign up with default email and password fields.
I added new fields/columns into the user model say username, designation and company.
So I have a profile view say with route '/users/1' and a link_to helper which would allow me to edit and update my user info.
By default i can only use users/edit route to edit my user info. How can i manage a new or separate edit and update option with different route say '/users/1/edit' from my profile view.
I read some posts before this but didn't help me. If anyone could outline things i should do. Thanks for reading :))
Edit:
routes file
root 'public#index'
devise_for :users
resources :users do
put 'users/:id/edit', to: 'users#edit'
end
user controller
class UsersController < ApplicationController
before_action :authenticate_user!
after_action :verify_authorized
before_action :set_user, only: %i[ show edit update ]
def index
#users = User.all
authorize User
end
def show
authorize #user
end
def edit
if current_user == #user
#user.update()
end
end
def update
authorize #user
if #user.update(secure_params)
redirect_to users_path, :notice => "User updated."
else
render 'edit'
end
end
private
def secure_params
params.require(:user).permit(:designation, :company,
:username)
end
def set_user
#user = User.find(params[:id])
end
end
In my view to go to edit:
<% if current_user.id == #user.id %>
<%= link_to 'Edit My profile', edit_user_path(#user), method: :edit,
class:"btn btn-primary" %>
<% end %>
If you really want to have a route user/:id/edit and not use the Devise default users/edit route(which edits the currently logged-in user). You can do the following:
Let's assume you have a users controller(if you don't have one, create one) and add an edit action to it which will handle the editing logic:
class UsersController < ApplicationController
# other code
def edit
user = User.find_by(id: params[:id]) # this id will be passed through the route
# Now here you need some authorization logic to prevent users from updating others.
# If you use CanCanCan, Pundit or any other authorization gem then write
# this logic there
if current_user == user
user.update() # do your update logic here with params you have
# render some json or whatever you want
else
# render some error messages in format you are using
end
end
end
This is the controller logic, now in your routes.rb file you need to register this route:
put 'user/:id/edit', to: 'users#edit'
This will edit the user with ID specified at :id.
Note again: This is not the approach I would take, I would rather just use the users/edit route and update the currently logged in user, but you wanted an example of this so do as you will

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.

Rails 4 - Admins editing another user's info

I want a simple system where users have three types (user, admin and medic) and the ones tagged Admin can "activate" other users' accounts so they can access certain privileges. I tried doing this with a simple button but I haven't found a way to do so.
The button for the code is:
<%= button_to "Change user Type to Medic", :method=> "activate_medic" %>
My activation method is as so:
def activate_medic
#user = User.find(params[:id])
#user.activated = true
if #user.save
flash[:info] = "Success"
end
end
And there's a post 'users/activate_medic' in my routes.rb file.
However, pressing the button brings up:
ActionController::RoutingError (No route matches [POST] "/users/1"):
If I'm trying to edit user 1.
Not exactly what I proposed in the original question, but:
I allowed the Admin users to completely edit a given user's info and by proxy their user type and permissions:
Users controller:
before_action :correct_user, only: [:edit, :update]
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user) || current_user.type == "Admin"
end
However, to avoid any user from just accessing their edit page and giving themselves admin rights, I edited the form to only allow Admins to see the field that edits their type:
<% if #user.type == "Admin" %>
<%= f.label :type, "User Type:" %>
<%= f.text_field :type, class: 'form-control' %>
<% end %>
The method option in link_to is meant to specify an HTTP verb, not the name of a custom method. Based on what you've provided, I would approach the problem like this:
button_to "Change user Type to Medic", activate_user_path(#user), method: :patch
And a dedicated controller:
class ActivateUserController
def update
#user = User.find(params[:id])
if #user.activate
flash[:info] = "Success"
end
redirect_to #user
end
end
And a route:
resources :users do
patch :activate, to: 'activate_user#update', as: :activate_user
end
And finally, move user behavior into the User model:
class User
def activate
self.update_attribute!(:active, true)
end
end

Rails 4 before_action for HTTP Post

I have a before action on a create method like so:
class ApplicationController
before_action :authenticate_user!
end
class PostsController < ApplicationController
def create
#post = Post.new(post_params)
if #post.save
redirect_to post_path(#post)
else
redirect_to root_path
end
end
end
#on some view while logged out, a user may see a form like so
<%= form_for :post, url: posts_path, method: :post do |f| %>
# some post data
<%= f.submit %>
<% end %>
I want to be able to be a logged out user and view a form that allows the guest to post. When the guest clicks the form submit button, I would like it to render the login form and after successful login to proceed to the create method.
Everything is working up until the point of proceeding to the posts#create method. Instead, it is rendering root_path. I am unsure if this is part of the desired functionality of before_action or not.
So, is it possible to have a before_action continue to a HTTP Post method with the original params from the form?
If you are using devise, you could add something like this to your ApplicationController
def after_sign_in_path_for(resource)
redirect_to request.referrer
end
This will ensure that every time after signin, you refer to the previous page.

Rails4: Restrict Controller Destroy Action

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)

Resources