Allow user to edit their own data with device - ruby-on-rails

I know there's probably solutions to this elsewhere, but I'm looking for help that works specifically in my case because I'm having a lot of trouble translating other solutions into my situation.
I currently have a device set up and the database is seeded so an admin is already created. Everyone else that signs up after that is a user.
There are two tables right now, a user table generated by rails and a cadet table. The cadet table stores information such as company, room number, class year and such.
My question is, how do I allow a user to edit/destroy only the cadet record that they've created? I know it seems like a big question but I've been looking all over and still can't find a reasonable way to implement this. Thank you!

Devise is related to authentication (who you are), you need a solution for authorization (who can do what). My suggestion is to go for CanCan (https://github.com/ryanb/cancan), which is a gem very widely use together wide Devise.
For your example, and after install the gem via Gemfile+Bundler:
Initialize the gem for your User model
rails g cancan:ability
it will create a file in app/models/ability.rb to define your restrictions
Define your restrictions, for instance:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (this line it to manage users not logged in yet)
if user
can :manage, Cadet, user_id: user.id
end
end
end
That will allow a user just to read, create, edit and destroy Cadets which user_id matches the id for the User.
Take a look at CanCan github page is wery well documented and with lot of examples; it's very simple to set up and works great.

You can also use a before_filter, something like the following:
class CadetsController < ApplicationController
before_filter :cadet_belongs_to_user, only: [:show, :edit, :update, :destroy]
....
private
def cadet_belongs_to_user
# following will work only on routes with an ID param
# but there are a few ways you could check if the cadet
# belongs to the current user
unless current_user && current_user.cadets.where(id: params[:id]).any?
flash[:notice] = "You are not authorized to view this page."
redirect_to root_path
end
end
end

Related

How to restrict the access to models for different user roles in rails (cancan gem)?

I want 3 user levels as Admin ,Manager,Customer in my rails application.
So i've created 3 devise models as admin,manager and customer.
And in my application there are models and controllers for product,delivery,services.
And I want to set access levels to each models.
So Admin have access to all models, controllers
Manager have access to Product, Delivery
Customer have access to Services
how can i write the ability model to match these requirements
I've written it as follows.Don't know whether it's correct.
class Ability
include CanCan::Ability
def initialize(user)
# Define abilities for the passed in user here. For example:
#
user ||= User.new # guest user (not logged in)
if user.admin?
can :manage, :all
elsif user.manager?
can :manage, product ,delivery
elsif user.customer?
can :manage, services
end
end
AND PLEASE HELP ME TO WRITE THE CODE FOR THE MODELS TO RESTRICT DIFFERENT USER ROLE ACCESS.
PLEASE BE KIND ENOUGH TO HELP ME!
I think the easiest way to tackle this would be in your controller. Let us say you had a book model with controller, and only wanted to allow admins to access this part. It's probably best to create a method in your bookings controller, and call it using the before action method:
class BookingsController < ApplicationController
before_action :check_admin
def check_admin
return unless admin_signed_in?
redirect_to root_path, error: 'You are not allowed to access this part of the site'
end
end
This will perform the check_admin method every time anything happens in the bookings controller, and will redirect to your root path unless an admin is signed in. Devise also comes with user_signed_in? and manager_signed_in? helpers if you've created models with those names.
You can tailor this more by deciding which controller actions will perform the action, for example
before_action :check_admin, only: [:edit, :create, :delete, :update, :new]
Would only perform the check before those controller actions, and allow anyone to access the index and show actions.

Use Devise's current_user in all associated controllers

I have a question about good practice. I am about to implement (just started) Pundit for my authorisation. However, in my controllers, say Projects and Stages I am associating all of my calls using current_user. e.g.
class ProjectsController < ApplicationController
before_action :authenticate_user!
before_action :set_project, only: [:show, :edit, :update, :destroy]
# GET /projects
# GET /projects.json
def index
#projects = current_user.Projects.all
authorize #projects
end
# GET /projects/1
# GET /projects/1.json
def show
end
# GET /projects/1/edit
def edit
end
private
# Use callbacks to share common setup or constraints between actions.
def set_project
#project = current_user.projects.find(params[:id])
authorize #project
end
# Never trust parameters from the scary internet, only allow the white list through.
def project_params
params[:title, :description]
end
end
I will have many more associations that are associated to a user (or many users). Is the above a good method to ensure that users can only view projects that they are allowed to? And is an authorisation 'gem' therefore not required?
Thanks in advance
Rudy
The short answer: yes, what you're doing with Devise's current_user is fine. As for an authorisation gem, you may or may not still need one.
The longer answer:
It's first worth briefly clarifying the difference between authentication and authorisation.
authentication is about saying "is this user who they say they are?". If I log in with the username 'bob', and also am able to correctly enter bob's password, then your app (via the help of devise) is able to say "OK, yep, I believe this user is who they say they are - I believe that it is in fact Bob on the end of the keyboard". That is to say, Bob is authenticated.
authorisation is about saying "is this user allowed to do what they are trying to do". For example, Bob may be able to create new projects, but he might not be allowed to delete projects. That is to say, Bob is authorised to create projects, and is not authorised to delete them.
OK. Now, authentication allows you, in and of itself, to perform some level of authorisation, because you can say "logged in users can view projects, but logged out users can't". Or you may want to say, as you are, "logged in users can view their own projects (but no one elses), but logged out users can't view any projects".
This last scenario is referred to as "scoping" by the logged in user, and the way you're doing it (using Devise's current_user) is just fine.
The last thing is the question of wether or not you need an authentication gem, in addition to the authorisation provided by Devise. If all you need to do is distinguish between logged in users and non-logged in users, then Devise should be all you need, and there's no need for an authentication gem.
If, on the other hand, you need more fine-grained authentication (for example, logged in users who are also admins can delete their own projects, but logged in users who are not admins can only view their own projects, and cannot delete them), then you'll likely want an authorisation gem, in addition to Devise.
Yes. It's ok if you don't need any advanced control features (like read-only).

How to add this specific authorization feature to my rails app?

My rails app has a few cab operators and they have a few cabs associated with them, and they are related as follows:
class Operator < ActiveRecord::Base
has_many :cabs
end
I have used Devise as my authentication gem. It authenticates users, admins and super admins in my app. I have created separate models for users, admins and super admins (and have not assigned roles to users per se).
I now wish to add the authorization feature to the app, so that an admin (who essentially would be the cab operator in my case) can CRUD only its own cabs. For e.g., an admins belonging to operator# 2 can access only the link: http://localhost:3000/operators/2/cabs and not the link: http://localhost:3000/operators/3/cabs.
My admin model already has an operator_id that associates it to an operator when an admin signs_up. I tried to add the authorization feature through CanCan, but I am unable to configure CanCan to provide restriction such as the one exemplified above.
I also tried to extend my authentication feature in the cabs_controller, as follows:
class CabsController < ApplicationController
before_action :authenticate_admin!
def index
if current_admin.operator_id != params[:operator_id]
redirect_to new_admin_session_path, notice: "Unauthorized access!"
else
#operator = Operator.find(params[:operator_id])
#cabs = Operator.find(params[:operator_id]).cabs
end
end
But this redirects me to the root_path even if the operator_id of the current_admin is equal to the params[:operator_id]. How should I proceed?
EDIT:
Following is my routes.rb file:
Rails.application.routes.draw do
devise_for :super_admins
devise_for :users
resources :operators do
resources :cabs
end
scope "operators/:operator_id" do
devise_for :admins
end
end
I have three tables: users, admins and super_admins. I created these coz I wanted my admins to hold operator_ids so that the admins corresponding to an operator can be identified. Also, I wanted the admin sign_in paths to be of the type /operators/:operator_id/admins/sign_in, hence the tweak in the routes file.
Unfortunately, initially I didn't understand that you actually have 3 different tables for users and (super)admins... Not sure that Pundit can help you in this case, but I'll keep the old answer for future visitors.
Coming back to your problem, let's try to fix just the unexpected redirect.
Routes seems fine, so the problem can be one of this:
You're getting redirected because you're currently not logged in as an admin, so you don't pass the :authenticate_admin! before_action.
You say "even if the operator_id of the current_admin is equal to the params[:operator_id]", but this condition is probably not true. Can you debug or print somewhere the value of both current_admin.operator_id and params[:operator_id] to see if they're actually equals?
Another interesting thing, is that you have a redirect for new_admin_session_path in your code, but then you say "this redirects me to the root_path". Can you please double check this?
OLD ANSWER
If you want to setup a good authorization-logic layer, I advice you to use pundit.
You've probably heard about cancan, but it's not supported anymore...
Leave Devise managing only the authentication part and give it a try ;)
PUNDIT EXAMPLE
First of all, follow pundit installation steps to create the app/policies folder and the base ApplicationPolicy class.
Then, in your case, you'll need to create a CabPolicy class in that folder:
class CabPolicy < ApplicationPolicy
def update?
user.is_super_admin? or user.cabs.include?(record)
end
end
This is an example for the update action. The update? function have to return true if the user has the authorisation to update the cab (You'll see later WHICH cab), false otherwise. So, what I'm saying here is "if the user is a super_admin (is_super_admin? is a placeholder function, use your own) is enough to return true, otherwise check if the record (which is the cab your checking) is included in the cabs association of your user".
You could also use record.operator_id == record.id, but I'm not sure the association for cab is belongs_to :operator. Keep in mind that in CabPolicy, record is a Cab object, and user is the devise current_user, so implement the check that you prefer.
Next, in your controller, you just need to add a line in your update function:
def update
#cab = Cab.find(params[:id]) # this will change based on your implementation
authorize #cab # this will call CabPolicy#update? passing current_user and #cab as user and record
#cab.update(cab_params)
end
If you want to make things even better, I recommend you to use a before_action
class CabsController < ApplicationController
before_action :set_cab, only: [:show, :update, :delete]
def update
#cab.update(cab_params)
end
#def delete and show...
private
def set_cab
#cab = Cab.find(params[:id])
authorize #cab
end
And of course, remember to define also show? and delete? methods in your CabPolicy.

How to allow only Admin (or some user) to edit the pages in Rails?

I have a scaffold Finances and I just realized that it can be edited by any logged in user by going to /finances/1/edit
I have installed activ_admin gem but I don't think it is what I need. How to make sure other than admin (or may be some users) no one can edit finances resource type- I
EDIT - I found https://github.com/EppO/rolify, is this best option or I still can do something better as it may be overkill ?
EDIT 1 - I went through this https://github.com/EppO/rolify/wiki/Tutorial and have assigned role "admin" to user = User.find(1), everything went well upto "ability.can? :manage, :all" in console, which shows TRUE for user 1 and false for other users. Now I am not able to figure out what to do ? I can still see all users being able to edit the page even though I have added "resourcify" in the finance.rb model. Any help ?
Well, I personally use rolify for my project and love it.. but to be honest this is super easy to achieve by simply adding a column "admin" to your User model and having it default to false. When you want a user to be an admin update the attribute to true and then require the User.admin==true to access the finances edit action... You can do this by redirecting the non-admin user from the controller (within the finances edit action)
By the way if you're using devise for auth check out Devise before_filter authenticate_admin?
I'm not sure how your models are set up, but lets say your User model has an admin column, you can do the following:
FinancesController < ApplicationController
before_filter :must_be_admin, only: :edit
def edit
...
end
private
def must_be_admin
unless current_user && current_user.admin?
redirect_to root_path, notice: "Some message"
end
end
end
You can add any actions needed to the before filter, e.g. before_filter :must_be_admin, only: [:edit, :destroy]
If you're looking to add sensible user authorization without rolling your own solution, definitely check out CanCan. Also helpful is this screencast by its author, Ryan Bates.

Using Devise with CanCan

This might be a bit of a noob question, but I'm asking anyway.
So I'm building an app where people make posts. So it's a social network.
But I don't want people to be able to edit and delete other's posts.
I don't think a role-based system would work here, because people only administrate their own posts only.
I was thinking some sort of AR association, but I don't know if that would work.
What I want is something like this for my app/models/ability.rb:
class Ability
def initialize(user)
if current_user.username == #post.username
can :edit, Post
can :destroy, Post
end
end
end
How would I go about doing this (assuming the models are User and Post)?
So basically should I do a User has Posts, or User has and belongs to Posts?
Use this
class Ability
def initialize(user)
can [:edit, :destroy], Post do |post|
post.try(:user) == user
end
end
end
The problem is that class Ability won't have access to the #post instance variable. So I think in Ability.rb you would have to say that users can :manage, Post so they can create, edit and destroy Post objects. Then it's up to your controller and model layers to ensure that they can only edit and destroy their own Posts.
With CanCan you can call load_and_authorize_resource at the top of a controller to protect the entire thing, but in your case your PostsController will probably need to protect at the action level. Calling authorize! :manage, #post in the create, destroy, edit and update actions, for example, along with checking to make sure that the current_user.username == #post.username, can ensure that people can modify only their own posts.
Finally, instead of actually deleting posts, you may want users to instead "soft-delete" by simply marking a Post as deleted. In this case, you would explicitly authorize the creation and editing of Posts, but you would not authorize them to actually destroy Posts. In your index and show actions, you would ensure that Posts marked as deleted were not shown.
As to role-based systems, down the road you may want a group of moderators or to add on another administrator. That's when you'll want a role-based system. I almost always make mine role-based to start with, even if it's just to have admin and user roles.

Resources