Making a Moderator Authorized User role for my app - ruby-on-rails

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.

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

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

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

Check if current_user is the owner of a resource and allow edit/delete actions

Example:
User A (id=10) has created a photo resource
photo: (id: 1 user_id = 10, url: "http://...")
Now, if User B (id=20) go to this url: /photos/1/edit it can edit photo of user A!!!
Rails+Devise provides something for this by default? It seems it's a very common issue
I just need to allow that any user can edit/delete ONLY resource it has created (where current_user == resource.user)
Using: Rails 4, Devise
Update:
I think CanCan it's something too advanced. I don't need roles or restrict some actions to certain users
In your PhotosController:
before_filter :require_permission, only: :edit
def require_permission
if current_user != Photo.find(params[:id]).user
redirect_to root_path
#Or do something else here
end
end
You can make use of Rails' associations and write it like this:
def edit
#photo = current_user.photos.find(params[:id])
# ... do everything else
end
This will only find a record when the photo with the supplied ID belongs to the current user. If it doesn't, Rails will raise a ActiveRecord::RecordNotFound exception.
Of course, I'm assuming the current_user method is available and your User model contains the statement has_many :photos.
Check this railscasts,
http://railscasts.com/episodes/192-authorization-with-cancan
Complications you will run into,
When you want cancan authorization on User Model that Devise gem is using for authentication
When you want to store your Roles in the Database
When you want to assign Permissions to the Roles as an Admin from the webUI
and more ..
Please comment if you want any of those features, I will be happy to help, because I recently did them with great help from others and its always amazing to pass it on.
A sample Ability for your resources can be like as follows,
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest users
send(user.role.name)
if user.role.blank?
can :read, User #for guest without roles
end
end
def man
can :manage, Photo
end
def boy
can :read, Photo
end
def kid
can :read, Article
end
end
I captured the exception from within a before_filter action:
before_action :set_photo, only: [:edit, :update, :destroy]
def set_photo
#photo = current_user.photos.find(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_to(root_url, :notice => 'Record not found')
end
Hope this helps someone. I'm using Rails 4 and Ruby 2.
So you are using gem devise.
This gem provides the current_user for the currently logged in user.
In your PhotosController#edit method. I'd do something like below.
def edit
#photo = Photo.find(params[:id])
redirect_to root_path, notice: 'Thou Shalt Nought duuu dat :(' unless current_user.id == #photo.user_id
...
end
This method is cheaper because you already have 2 objects to compare instead of running a query in the comparison.
The simplest would be to to modify routes.rb.
Assign photos to live in the current_user path.
For example,
devise_for :users
resources 'users' do
resources 'photos'
end
cancan is difficult and complicate
i have coding is_onwer method
it's very simple, easy
https://gist.github.com/x1wins/0d3f0058270cef37b2d3f25a56a3745d
application controller
def is_owner user_id
unless user_id == current_user.id
render json: nil, status: :forbidden
return
end
end
def is_owner_object data
if data.nil? or data.user_id.nil?
return render status: :not_found
else
is_owner data.user_id
end
end
your controller
before_action only: [:edit, :update, :destroy] do
is_owner_object #article ##your object
end
If CanCan is too advanced, you should loon into checking the id of the accessor in the controller using...
if #user.id == #photo.user_id
# edit photo details
else
redirect_to root_path, notice: "You! Shall! Not! Edit!"
...or something like that
Write another before_filter in application_controller:
before_filter :has_permission?
has_permission?
controllers=["articles", "photos", "..."]
actions=["edit", "destroy", "..."]
id = params[:id] if (controllers.include?(params[:controller] && actions.include?(params[:action]) end
if id && (current_user.id==(params[:controller][0...1].capitalize!+params[:controller].singularize[1...-1] + ".find(#{id}).user_id").send)
return true
else
redirect_to root_url, :notice=>"no permission for this action"
end
helper_method :has_permission?
And you can use it in views, not to show users link they can't follow.
Some kind of this, of course you need to modify it to suit your needs.

Understanding Cancan abilities

Trying to get Cancan securing a few models in an application and curious why it's not working the way I thought it would. I had thought you could can? on the specific instance as opposed to the entire class so, not in this example but, you could enable abilities on a per instance basis as a list of posts are displayed?!?
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.role? :admin
can :manage, :all
elsif user.role? :moderator
can :manage, Post
else
can :read, :all
end
end
end
# posts/index.html.haml
...
- if can? :update, #post <- doesn't work
- if can? :update, Post <- works
Edit: add PostsController.rb
#posts_controller.rb
class PostsController < ApplicationController
before_filter :login_required, :except => [:index, :show]
load_and_authorize_resource :except => [:create]
def index
# #posts = Post.all ## <- handled by Cancan's load_and_authorize_resource
#events = Event.where("end_date <= :today", :today => Date.today)
#next_event = Event.next
respond_to do |format|
format.html # index.html.erb
format.json { render json: #posts }
end
end
...
end
This line:
- if can? :update, #post <- doesn't work
Is asking CanCan "can I update this specific post." You defined the ability in terms of all posts. If you had done:
can :update, Post, :user_id => user.id
Then your "if can?" would work, and the user would only be able to update their own posts. So you want to use the specific resource version ("#post") if something about this instance of the resource determines the permission, and you want to use the class version ("Post") if the user has the ability for all instances of the class.

Resources