Pundit throws undefined method `user' - ruby-on-rails

I cannot restrict the Project model view to a owning user. Cannot figure this one out:
Error:
undefined method `user' for #<Project::ActiveRecord_Relation:0x007f94b25dd010>
project_policy.rb
class ProjectPolicy < ApplicationPolicy
def show?
user.present? && user == record.user
end
end
Projects controller
class ProjectsController < ApplicationController
def show
#project = Project.find(params[:id])
#pages = #project.pages
authorize #projects
end
If I remove the user == record.user all works fine
application_policy file is default
Project belongs to User
User has many Projects
project.user in the console works fine.

undefined method `user' for Project::ActiveRecord_Relation:0x007f94b25dd010
Firstly, I'd assume that #projects is defined somewhere in your code using a before_filter and also I assume its returning a collection of records. If so then here is the issue
user.present? && user == record.user
Here the record in record.user will be a collection of records not a single record. So record.user just fails with that error.
Changing authorize #projects to authorize #project should solve your problem

Related

Rails - Handle User roles using pundit

I have a table of users with enum user_type [Manager, Developer, QA]. Currently, I'm handling sign in using Devise and after login I'm using the following logic to display the appropriate webpage:
class HomeController < ApplicationController
before_action :authenticate_user!
def index
if current_user.manager?
redirect_to manager_path(current_user.id)
end
if current_user.developer?
redirect_to developer_path(current_user.id)
end
if current_user.quality_assurance?
redirect_to qa_path(current_user.id)
end
end
end
I want to use pundit gem to handle this. From the documentation, it transpired that this logic will be delegated to policies but I can't figure out how. Can somebody help me in implementing pundit in my project?
This is my users table:
I have created a user_policy but its mostly empty:
class UserPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.all
end
end
end
User model:
You want to use Pundit to authorize a user, as in check if that user should be allowed to visit a controller action. If the user is not authorized for a specific action it raises a Pundit::NotAuthorizedError
You can check if a user is allowed to perform an action in the pundit policy, in which you have access to record (the instance thats passed to authorize) and user. So assuming you have a Flat Model, where only the owner can edit the Flat you might do this:
# flats_policy.rb
def edit?
record.user == user
end
Now lets say you also want to allow admins to edit you might do this
# flats_policy.rb
def owner_or_admin?
record.user == user || user.admin # where admin is a boolean
end
def edit?
owner_or_admin?
end
and the controller:
# flats_controller.rb
def edit
#flat = Flat.find(params[:id])
authorize #flat
# other code here
end
Now the index action is the odd one out because you would essentially have to call authorize on each instance, so the way Pundit handles this is with the Scope:
# flats_policy.rb
class Scope < Scope
def resolve
scope.all
end
end
and a corresponding index action might look like:
def index
#flats = policy_scope(Flat) # note that we call the model here
end
So lets say a user can only see flats that he/she owns:
# flats_policy.rb
class Scope < Scope
def resolve
scope.where(user: user)
end
end
and if admins can see all flats:
# flats_policy.rb
class Scope < Scope
def resolve
if user.admin
scope.all
else
scope.where(user: user)
end
end
end
In any case if the user is not allowed to perform an action you can rescue from the error like so:
# application_controller
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
def user_not_authorized
flash[:alert] = "You are not authorized to perform this action."
redirect_to(root_path)
end
I guess you could do some dirty redirecting here, as in send admins to an admins_root_path, users to a default_root_path and so on...
On a final note, since this post is already too long you can check a policy in the view like this:
<% if policy(restaurant).edit? %>
You can see me if you have edit rights
<% end %>

Adding Devise to Existing App - undefined method `sign_in' for #<OmniAuth::Strategies::Standard>

I am adding Devise to an existing application. The authentication file is inside lib/omniauth/strategies/standard.rb. There is also multiple lib/omniauth/strategies/clientname_saml.rb files for the app (one per Apartment tenant). Due to the current sprint, I'd like to add Devise gradually and thus keep the current user.authenticate method. We're using a number of gems which automatically recognize the #current_user which Devise sets.
I've tried adding sign_in(:user, user) in the callback_phase below, but I get the error: NoMethodError (undefined method `sign_in' for #<OmniAuth::Strategies::Standard>)
module OmniAuth
module Strategies
class Standard
include OmniAuth::Strategy
include Rails.application.routes.url_helpers
def request_phase
if strategy_supported?
redirect(new_session_path(request.params))
else
fail!('strategy_unsupported')
end
end
def callback_phase
# binding.pry
if strategy_supported?
email = standard_params[:email] || ''
user = User.find_by(email: email.downcase)
if user && !user.locked? && user.authenticate(standard_params[:password])
sign_in(:user, user)
super
else
redirect(sessions_failure_path(safe_params))
end
else
fail!('strategy_unsupported')
end
end
private
def standard_params
request.params.symbolize_keys.slice(:email, :password)
end
def safe_params
request.params.symbolize_keys.slice(:email, :code, :target)
end
def strategy_supported?
Preference.get(:login_strategy) == 'standard'
end
end
end
end
I was able to solve it by using include Devise::Controllers::Helpers.

Cannot complete the New controller action through Pundit gem

I have a Ruby on Rails web app where users creates notes. The note belongs to the user that created it.
I installed the Pundit gem to create authorizations, specifically an admin role.
I would like for a user to be able to:
create, update, or delete their notes
And for an admin to be able to do the same for any user's notes.
When I log in as an admin, I can create a new note. When I log in as a member, I cannot create a new note. I get instantly redirected to the root page, and never even brought to the new note page.
Here is the flash error message I receive:
not allowed to new? this #<Note:0x007fe2ca17bc18>
Before installing Pundit, members were able to create a new note. So I assume it has something to do with my policies.
Here are the relevant methods from my application_policy.rb file:
def index?
false
end
def show?
scope.where(:id => record.id).exists?
end
def create?
user.present?
end
def new?
create?
end
def update?
user.present? && (record.user == user || user.admin?)
end
def edit?
update?
end
def destroy?
update?
end
Here is my note_policy.rb file:
class NotePolicy < ApplicationPolicy
def create?
user.present? && (record.user == user || user.admin?)
end
def new?
create?
end
def update?
create?
end
def destroy?
update?
end
end
And here are the relevant actions from my notes_controller.rb file:
def new
#note = Note.new
authorize #note
end
def create
#note = Note.new(notes_params)
#note.user = current_user
authorize #note
if #note.save
redirect_to notes_path
else
render :new
end
end
I am trying to figure out why an admin can create a new note, but a member cannot.
Thank you.
In the new action, the authorization check is being made using NotePolicy#create?. See chain of calls below:
NotePolicy#new? --> ApplicationPolicy#new? --> NotePolicy#create?
A new Note (#note = Note.new) won't pass the authorization check made in NotePolicy#create?.
Add a new? method to NotePolicy that implements the authorization logic you want.
ADDED
This create? method will not pass for a new note (Note.new).
def create?
user.present? && (record.user == user || user.admin?)
end
To confirm why that's the case you can debug by adding a line like puts "#{user.present?} #{record.user} #{user}" at the beginning of the create? method.
You'll see the values of these variables in the server output or logs/development.log.
Is it missing a new? method in your NotePolicy?

add a column value before saving item to database

Say user submits a form (creates new item in his account).
Before it goes to database - I want to do this:
params[:user] => current_user.id
# make a note of who is the owner of the item
# without people seing this or being able to change it themselves
# and only after that to save it to database
What's the best way to do it?
All I see in controller is this:
def create
#item = Item.new(params[:item])
...
end
And I'm not sure how to change values under params[:item]
(current_user.id is Devise variable)
Tried to do this:
class Item < ActiveRecord::Base
before_save :set_user
protected
def set_user
self.user = current_user.id unless self.user
end
end
And got an error:
undefined local variable or method `current_user'
It should be as simple as:
def create
#item = Item.new(params[:item])
#item.user = current_user
#...
end
You're getting an undefined local variable or method current_user error as current_user is not available in the model context, only controller and views.

Rails - session request is nil error

I have a method that I want to execute some search logic, and then save a Search object that has the searched string and user id of the person who did the search.
The search/save logic seems to be working fine otherwise, but when I try to get the current user (using a method from the application controller) it throws a runtime error that has to do with the session:
ActionController::Metal#session delegated to #_request.session, but #_request is nil: #<SearchController:0x1038e32e0 #action_has_layout=true, #view_context_class=nil, #_status=200, #_headers={"Content-Type"=>"text/html"}>
Here's the method in the search controller:
class SearchController < ApplicationController
...
def factualsearch(search)
if search
searchquery = Search.new
# this causes the error
if current_user
searchquery.user = current_user
end
searchquery.search_string = search
searchquery.save
...
end
#results
end
end
Here's the current_user method I'm trying to call from my application controller:
def current_user
return unless session[:user_id]
#current_user ||= User.find_by_id(session[:user_id])
end
helper_method :current_user
Here's the pages controller where I'm calling the method:
class PagesController < ApplicationController
...
def search
searchcontrol = SearchController.new
#results = searchcontrol.factualsearch(params[:search])
end
...
end
Not exactly a direct way to fix this problem, but I was able to get around it by have the function accept the user in addition to the search query, rather than trying to call the current_user method from inside it. The class that calls the action can access the current_user method just fine.

Resources