I'm working on Authorization for my school assignment, which is a Reddit clone. I was just introduced to the Pundit Gem for Authorization on user roles, ie, Admin, Moderator, Member and Guest.
I have to make it so:
Admins and Moderators should see all posts, members should only see their own posts, and guests should see no posts.
Sign in as a normal user, and you should only see the posts you've created.
application_policy.rb
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def index?
false
end
def show?
scope.where(:id => record.id).exists?
end
def create?
# Checks if user exists and is logged in
user.present?
end
def new?
create?
end
def update?
# Checks if user is logged in, the owner or admin
user.present? && (record.user == user || user.admin?)
end
def edit?
update?
end
def destroy?
update?
end
def scope
record.class
end
end
Here is what I am working on:
This will check if a user is present, and if the user is a moderator or administrator and only grant them access to view posts. Works just like the instructions state.
post_policy.rb
class PostPolicy < ApplicationPolicy
def index?
user.present? && (user.moderator? || user.admin?)
end
end
Now if I look back at my application_policy.rb I can see this line here, "Checks if the user is logged in, the owner, or admin":
user.preset? && (record.user == user || user.admin?)
If I try to add this into my authorization of index? I will keep getting a
"NoMethodError in PostsController#index"
class PostPolicy < ApplicationPolicy
def index?
user.present? && (user.moderator? || user.admin? || record.user == user)
end
end
Thank you.
Use scopes: https://github.com/elabs/pundit#scopes
In your case PostPolicy.rb should look like this:
class PostPolicy < ApplicationPolicy
def index?
true
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
if user.admin? || user.moderator?
scope.all
else
scope.where(user: user)
end
end
end
end
Related
I am currently using Pundit as my authorization gem on my Rails app. Basically, I created this page where in if you are admin (which means if a field has the admin value set up to true) you can view this page and delete some existing user on the database viahttp://localhost:3000/dashboard/users`
I already place this on my menu:
<% if current_user.admin === true %>
<%= link_to "Users List", users_path, class: "dropdown-item #{active_class_white('/dashboard/contacts')}" %>
<% end %>
Which will hide this link if the user is not admin. However, this can be still access if the non-admin visits the url directly.
I've already set up my pundit file via application_pundit.rb file which contains the ff:
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def index?
false
end
def show?
scope.where(:id => record.id).exists?
end
def create?
false
end
def new?
create?
end
def update?
false
end
def edit?
update?
end
def destroy?
false
end
def scope
Pundit.policy_scope!(user, record.class)
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
scope
end
end
end
Now I don't know what to do how to prevent other users that doesn't have the admin access to view this page and be able to delete any users from the database.
Please help.
UPDATE: I created a user_policy.rb and place the ff codes:
class UserPolicy < ApplicationPolicy
def index?
user.admin?
end
def destroy?
user.admin?
end
end
But this did not work as a non-admin can still access the page. What else should I do?
In user_policy:
class UserPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.all
end
end
def index?
user.admin?
end
end
In the context of Pundit, user is current_user so this will evalue to true if user is an admin (and to false otherwise).
And in your controller:
def index
#users = policy_scope(User)
authorize User
end
I am using Pundit for authorization for my User model.
My goal is to extend this to use my AdminUser model, specifically for my admin namespace.
By default, Pundit checks for a "user" or "current_user". How can I change this to check for a "admin_user" or "current_admin_user", based on Devise?
policies/admin/admin_policy.rb (Closed system, currently looks for User instead of AdminUser)
class Admin::AdminPolicy
attr_reader :user, :record
def initialize(user, record)
# Must be logged in
raise Pundit::NotAuthorizedError, "You must be logged in to perform this action" unless user
#user = user
#record = record
end
def index?
false
end
def show?
false
end
def create?
false
end
def new?
create?
end
def update?
false
end
def edit?
update?
end
def destroy?
false
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
raise Pundit::NotAuthorizedError, "You must be logged in to perform this action" unless user
#user = user
#scope = scope
end
def resolve
scope.all
end
end
end
policies/admin/home_policy.rb (Example sub-policy of the Admin namespace)
class Admin::HomePolicy < Admin::AdminPolicy
def index?
user.present?
end
end
I think you need to define the method pundit_user on your controllers to customize it https://github.com/varvet/pundit#customize-pundit-user
def pundit_user
current_admin_user
end
I've had a long break from my 2 years of effort in trying to learn how to use pundit in my rails app. I'm back and trying to learn how to use pundit.
I've made a completely new rails 5 app and installed pundit.
I have a user resource, an application policy and a user policy. Each has:
Users controller:
def index
# #users = User.all
#users = policy_scope(User)
end
Application Policy
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def index?
true
end
def show?
scope.where(:id => record.id).exists?
end
def create?
false
end
def new?
create?
end
def update?
false
end
def edit?
update?
end
def destroy?
false
end
def scope
Pundit.policy_scope!(user, record.class)
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
scope
end
end
end
User policy
class UserPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.where(user: user)
end
end
end
Then in my user index, I'm trying to follow the instructions in the pundit gem docs, by doing:
<% policy_scope(#users).each do |user| %>
I get this error:
PG::UndefinedColumn: ERROR: column users.user does not exist
LINE 1: SELECT "users".* FROM "users" WHERE "users"."user" = '566119...
^
: SELECT "users".* FROM "users" WHERE "users"."user" = '566119d2-54d8-4ab2-b7c5-f17c80b517f3' AND "users"."user" = '566119d2-54d8-4ab2-b7c5-f17c80b517f3'
Can anyone see how I'm getting off to the wrong start? I haven't even tried to define my scope in the way I want to yet, but it isn't working at this point.
class UserPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.where(user: user)
end
end
end
scope.where means the User class (user.where), because it's inside the user policy.
The user value is the current user, inherited by the application scope.
The scope example that you posted i'm guessing that you want to show only current user records. Then you can do the following as the comment above:
scope.where(id: user.try(:id))
Also because you have defined a scope in the controller
def index
# #users = User.all
#users = policy_scope(User)
end
You don't need to define another one in the view.
<% policy_scope(#users).each do |user| %>
It's one or the other. In the view you can simply do users.each do....
I'm doing this in the User#Show view:
<% if policy(Gallery.new).create? %>
<%= link_to "Add a photo gallery for #{#user.name}", new_user_gallery_path(#user), class: 'btn btn-success' %>
<% end %>
and Admin can add galleries to any user. But nobody else can.
Here's the Gallery Policy:
class GalleryPolicy < ApplicationPolicy
def create?
user.present? && (record.user == user || user.admin?)
end
def new?
create?
end
end
Here's the Application Policy:
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def index?
true
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
def scope
record.class
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
scope
end
end
end
As you can see, a user should be logged in and the record should belong to them or they should be admin for them to create galleries. What am I doing wrong?
does specifying the Policy this way
if policy(Gallery).create?
change the outcome?
I'm trying to create a show view for three roles. Admin, super user, and user. An admin should see all of the users. A super user should see only users and a user should not see anyone. When I used the commented out policy method in the resolve for else user.super_user? would give me unsupported: TrueClass error. Any suggestions are welcomed.
Users Controller
def index
#users = policy_scope(User)
authorize User
end
User Policy
class UserPolicy
attr_reader :current_user, :model
def initialize(current_user, model)
#current_user = current_user
#user = model
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
if user.admin?
scope.all
else user.super_user?
scope.where(user.role = 'user' )
# scope.where(user.role != 'admin') [this would not work in the views, but it would work in rails c]
end
end
end
def index?
#current_user.admin? or #current_user.super_user?
end
end
updated Users Controller
class UsersController < ApplicationController
before_filter :authenticate_user!
after_action :verify_authorized
def index
#users = policy_scope(User)
end
end
Correct Answer
I figured out what I needed to do. I was calling the role incorrectly. Updated scope below.
class UserPolicy
attr_reader :current_user, :model
def initialize(current_user, model)
#current_user = current_user
#user = model
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
if user.admin?
scope.all
else user.super_user?
scope.where(role: 'user' )
end
end
end
def index?
#current_user.admin? or #current_user.super_user?
end
controller
class UsersController < ApplicationController
before_filter :authenticate_user!
after_action :verify_authorized
def index
#users = policy_scope(User)
authorize #users
end
Your resolve method should use elsif:
# Safer option
def resolve
if user.admin?
scope.all
elsif user.super_user?
scope.where(user.role = 'user' )
else
scope.none
end
end
or not check for the super user at all and just depend on checking the authorization of the user before the result is used:
# This option is the same as the code you added to your question
# but doesn't include the unnecessary check
def resolve
if user.admin?
scope.all
else
scope.where(user.role = 'user' )
end
end
EDIT: updated to deal with the case of not being an admin or super user