How to allow Admin user to access customer user objects rails - ruby-on-rails

I have a report object that belongs_to a customer(user_type). I have it setup so that the customer is able to grant or remove permission from an admin to see the customers report. I need to know what steps or direction do I need to take to get the reports viewable to the admin user. The admin user should only have the ability to see reports (show) and see an index of reports that they have access to.
I am thinking maybe I should create a new controller but I am not sure if that is correct or how to do another controller tied to an object that already has a controller.

I highly suggest looking into the CanCanCan gem (the continuation of the CanCan gem which is no longer supported). In this way your code would look something like this in the end (you didn't post any code, so I have to make assumptions about your variables and such):
<% if current_user.admin? %>
<% if can? :view_report, #customer %>
<!-- Render the report to this admin -->
<!-- ............................. -->
<% end %>
<% end %>
And then you would have something like this in your controller to give an admin permission to view the user's report:
def handle_report
can [:show], Report, :user_id => user.id
if current_user.admin?
can :manage, :current_report
end
end
Again, apologies for guessing the variables.

Related

Rails 5 with CanCanCan - How to filter list for an index action

I'm making a system for study purposes.
This system controls the hospital shifts for the user. An user should only see his shifts.
I'm using CanCanCan for the authorization control.
So for the index page the controller action I have:
class UsersController < ApplicationController
def index
#users = User.all
end
end
In the index.html.erb
<% #shifts.each do |shift| %>
<% if can? :read, shift %>
<tr>
<td><%= shift.date %></td>
</tr>
<% end %>
<% end %>
In the ability I have:
class Ability
include CanCan::Ability
def initialize(user)
if user
can :manage, Shift, user_id: user.id
end
end
end
I want to show a message to the user if he doesn't have any shifts.
But using the can method in index html page because if the list is not empty but there is no shift the current user can see I can't use an empty method direct in the #shift.
Is it possible to filter the list in the index action in user controller?
You're asking a few different questions here, and it's hard to answer them individually, so I'm going to take a more broad approach in hopes of answering some of the underlying questions I believe you're having.
First thing's first: Cancancan does not do anything that relates to querying your database. Cancancan leaves that up to you.
I'm not seeing where #shifts is defined, so I'm going to assume it's in your ShiftsController — whereas the index action is supposed to show a given user's shifts.
A given user is usually the current_user in Rails applications — this authentication is done by something such as Devise. If your associations are setup correctly, you're able to do #shifts = current_user.shifts
But in the case that you want to, say, filter the shifts by user — such as having a RESTful route of /users/:id/shifts — you would have routes that look like this:
resources :users do
member do
get :shifts => 'users#shifts'
end
end
which would map to your UsersController with a method shifts.
Inside that method you'd have an :id param with the value that you'd expect from your show action.
#shifts = Shift.where(:user_id => params[:id])
Now, if you wanted to filter shifts on your ShiftsController#index method, you could have something like this:
#shifts = Shift.where(:user_id => params[:user_id])
and your URL would look like, presumably, /shifts?user_id=1
Now, if a user doesn't have any shifts, #shifts will return an empty array — indicative of no rows that match your database query. Therefore, you can do some simple logic in your view,
<% if #shifts.empty? %> No shifts. <% else %> ... things to list shifts <% end %>

Ruby No Method Error on a Model for Current User when Logged Out

I'm building a rails app that has a lot of things that change based on whether or not the user has completed certain quizzes. Everything works fine when a user is logged in, but I just tried logging out and I got a NoMethodErroron this line of code:
<% if current_user.per_quiz.nil? %>
Basically, if the user has completed per_quiz they go to one page (the edit view), if they haven't they go to a different page (the new view). The only thing that changed was the fact that I logged out, so I can only assume that is what caused the error. Do I need to add some kind of if statement to account for a state in which no user is logged in? How should I fix this error in accordance with Ruby best practices?
You just need to check to see if current_user is set before checking per_quiz. You can handle checking for the login state easily in a view by querying current_user.nil?:
<% if !current_user %>
<p> You must be logged in to do anything useful. Please go login</p>
<% elsif !current_user.per_quiz %>
<p>Cool quiz stuff goes here...</p>
<% else %>
What you probably really want is to have a logged out user go elsewhere, such as the home page or signin page. To do that, you need to do a couple of simple things to your controller. I'm going to assume that the controller is called 'QuizzesController' since you hadn't included your controller code in the question.
Here's how to do it:
class QuizzesController < ApplicationController
# Other devise authentication logic goes here...
before_action :authorize_user!
# Your actions go here...
private
def authorize_user!
if !current_user
redirect_to '/', notice: "You must be logged in to access to this page"
end
end
end
What this does is install a "before_action" handler that will check that the user is logged in before letting them do anything in this controller. If they are not logged in, you can redirect them wherever you wish.
Note that sometimes, only certain actions need this kind of treatment. In that case, you can use an :only or :except option to specify which action(s) are or are not handled. It looks like this:
before_action :authorize_user!, only: [ :new, :edit, :create ]
or like this:
before_action :authorize_user!, except: :list
This will give you greater flexibility in managing the authorization part of the equation, where devise handles the authentication part.
That's because current_user is nil when no user logged in. And if you will cal some method on nil it will throw NoMethodError.
I assume that have code you provided in the view. So, you can check if user is logged in.
<% if user_signed_in? %>
# do smth with user quizzes
<% else %>
# do something else
<% end %>
But the best way is to use before filter in the controller and not allow to anonymous to see that page.
Here is the example application with Rails and devise. https://github.com/RailsApps/rails-devise
Hope that helps.
I'm not sure if this is the "correct" Ruby way to do this, but the way I eventually found was to change it to an if/elsif/else statement:
<% if current_user.nil? %>
...
<% elsif current_user.bal_quiz.nil? %>
...
<% else %>
...
<% end %>
Probably should have figured this out before I posted the question, but I was (and still am) interested to see if there's a better or "more Ruby" way of doing this.

Rails - Using different views for anonymous vs. logged in users: Bad idea?

My admin accounts are user accounts with a simple boolean set to true. Works fine for now, I can control functionality with if statements in the views, example:
<td><% if current_user.admin? || current_user == user %>
<%= link_to "Edit", edit_user_path(user) %>
<% end %></td>
Some resources are off limits to anonymous users, and they get redirected to the login page if they try and select those links. But other resources (like a list of all the articles on the site), I want both those with a session and those without to see. The problem of course is that a user with no session will throw an error, because there is no current_user if you don't have a session. So I decided to divide up the world into the 2 parts. When you hit the index.html.erb for 'articles', this is all that in there:
<% if current_user == nil %>
<%= render "anonindex" %>
<% else %>
<%= render "authindex" %>
<% end %>
My question is, am I making a long term design mistake by doing this? Am I eventually going to need to implement a roles based system, or is it feasible to differentiate user privileges based on boolean operators, and keep the users with no session in a completely separate sandbox? Its working great thus far, but I worry I'm going down a path that will require a total rebuild later.
You don't actually have to check this thing in views. You can check this thing in Controller, and can take the appropriate out there:
class YourController < ApplicationController
before_action :check_user_logged_in, only: [:index, :your_desired_method]
end
And then in check_user_logged_in method, you can see if a user is logged in, send him to the desired place, otherwise redirect him to the log in page.
def check_user_logged_in
redirect_to log_in_path unless current_user
end

Rails authority apparent inconsistency

I am using the gems authority and rolify to manage user permissions on a set of subjects.
Each subject can be seen by a user only if the user has the :admin role for that subject.
Code in the view:
<% if (current_user.has_role? :admin, #subject) %>
ADMIN
<% end %>
<% if #subject.readable_by?(current_user)%>
#some other code
<% end %>
Code in the authorizer:
class SubjectAuthorizer < ApplicationAuthorizer
# can the user view the subject?
def self.readable_by?(user)
user.has_role? :admin, #subject
end
end
My problem is that the ADMIN part is displayed, but not the rest of the page. However, the two if conditions should have the same truth value. Can anyone spot a mistake?
#subject is not available in the Authorizer. You need to use resource instead.

Rails 4: form_for with nested resource and without

In my app, when a regular user logs in, they are dropped in on a dashboard that displays Service Requests for the company they belong_to.
When an admin logs in, they are dropped onto a dashboard that displays all of the company logos that can login and file Service Requests.
The views between a regular user and an admin user are virtually the exact same, outside of one or two entities on the form (which are controlled via cancan). So I am trying to be able to use the same form for if an admin creates an SR or a regular user creates an SR.
Routes.rb:
resources :service_requests do
resources :notes
end
namespace :admin do
get '', to: 'dashboard#index', as: '/'
resources :companies do
resources :service_requests, only: [:index, :new]
end
end
If an admin logs in and clicks on a company logo and clicks to create a new SR, the route is /admin/companies/1/service_requests/new. If a regular user logs in it is, /service_requests/new. I am just slightly confused on how to reuse the same form for both the admin and non-admin side. Because I am setting the company_id on the SR in my create resource in the ServiceRequestsController
I do something like the following to use the same form on an admin and a not admin using cancan
#models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
if user.is_admin? #replace this by something that returns true if logged-in user is admin
can :generate_this_form_fields, User
# ^ normally can -> points to a method in the controller, but you can
# create one even if you're not validating any method, just so you could use it anywhere
end
end
end
While on the view file where your form is, something like
#views/form.html.erb
<%= form_tag ... %>
<% if can? :generate_this_form_fields, User %>
This is where the fields are displayed if admin account
<% else %>
This is where the fields are displayed if not admin
<% end %>
<% end %>
Actually in the view, you could just use
<% if current_user.is_admin? %>
And not
<% if can? :generate_this_form_fields, User %>
But I've made the example in such a way that might fit to what you're trying to solve.
Hope this helps. Cheers!

Resources