I have Post model with published? field and some authorization system which defines admin? method inside ApplicationController.
I want to restrict access to unpublished posts and show them only to administrator.
I tried to define a scope accessible to return only published posts to users, but all posts for administrator.
scope :published, where(:published => true)
def self.accessible
admin? ? all : published
end
The problem is that admin? method can't be accessed inside the model. What is the best way to implement what I want?
# option 1
class Post < ActiveRecord::Base
def self.accessible_to user
user.admin? ? all : published
end
end
class PostsController < ApplicationController
def index
#posts = post.accessible_to current_user
end
end
# option 2
class Post < ActiveRecord::Base
def self.accessible is_admin
is_admin ? all : published
end
end
class PostsController < ApplicationController
def index
#posts = post.accessible admin?
end
end
One way, but not so abstract.
def self.published_unless(condition)
condition ? all : published
end
Post.published_unless(admin?)
Related
How to use scopes, defined in the model, in my Pundit policy?
In my model I have a scope:
scope :published, ->{ where.not(published_at: nil )}
And in my Pundit policy I have
class CompanyPolicy < ApplicationPolicy
def index?
true
end
def create?
user.present?
end
def new?
true
end
def show?
true
end
def update?
user.present? && user == record.user
end
end
How can I use my scope in the Pundit policy? I would like to show it only if it's "published", something like this, which doesn't work at the moment:
class CompanyPolicy < ApplicationPolicy
def show
record.published?
end
end
Scopes are Class methods, you can't call them on instances.
You have to define a published? instance method too:
def published?
published_at.present?
end
You could use the scope if you ask if the record exists on the given scope with:
User.published.exists?(user.id)
And it will return true if the scope includes the user id, but I wouldn't recommend that, it requires an extra query to the database to get something that you can know from the user instance you already have.
More specifically, if I have two options: do a check in the controller or override an association method in the model, which one should I prefer?
Edit:
class Book < ApplicationRecord
belongs_to :author
def author
super || build_author
end
end
Is the code above ok or should I prefer another solution like the one below?
class BooksController < ApplicationController
def update
set_author
if #book.update_attributes(params[:book])
#redirect
else
#render show page - unprocessable entity
end
end
def set_author
a = Author.where(fields)
#book.author = a || #book.build_author
end
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....
In my app I have Permission table, which stores all the logic what User can do. With Pundit I want to allow User to create new Campaign if Permission table allows. User can access Campaigns and create new, if Permission table contains this info:
permitable_type: Sysmodule // another table where I store info on System sections, where Campaigns is one of
permitable_id: 2 // means Campaigns from Sysmodule
level: 3 // means User can edit something in Campaigns section
So far I keep getting error "Pundit::NotDefinedError", unable to find policy of nil policies/application_policy.rb is standart, no changes.
Obviously I am doing sothing wrong. How do I do this authorization correctly? Many thanks for any help! I am on Rails 5 + Pundit.
models/permission.rb
class Permission < ApplicationRecord
belongs_to :permitable, polymorphic: true
belongs_to :user
enum level: {owner: 1, view: 2, edit: 3}
end
models/user.rb
has_many :permissions
has_many :campaigns, through: :permissions, source: :permitable, source_type: 'Campaign' do
def owner_of
where('`permissions`.`level` & ? > 0', Permission::owner )
end
end
has_many :sysmodules, through: :permissions, source: :permitable, source_type: 'Sysmodule' do
def can_access
where('`permissions`.`level` & ? > 1', Permission::can_access )
end
end
controllers/campaigns_controller.rb
def new
#campaign = Campaign.new
authorize #campaign
end
policies/campaign_policy.rb
class CampaignPolicy < ApplicationPolicy
attr_reader :user, :campaign, :permission
#user = user
#permission = permission
end
def new?
user.permission? ({level: 3, permitable_type: "Sysmodule", permitable_id: 2})
end
views/campaigns/index.html.erb
<% if policy(#campaign).new? %>
</li>
<li><%= link_to "New campaign", new_campaign_path(#campaign) %></li>
</li>
<% end %>
Instead of dealing directly with what permissions a user should have try thinking of it what roles users can have in a system.
This makes it much easier to create authorization rules that map to real world problems.
Lets imagine an example where we have users and groups. The rules are as follows:
groups can be created by any user
the user that creates the group automatically becomes an admin
groups are private
only admins or members can view a group
only an admin can modify a group
The models:
class User < ApplicationRecord
rolify
end
class Group < ApplicationRecord
resourcify
end
The policy:
class GroupsPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.with_roles([:admin, :member], current_user)
end
end
def show?
user.has_role?([:member, :admin], record)
end
def index?
true
end
def create?
true # any user can create
end
def new?
create?
end
def update?
user.has_role?(:admin, record)
end
def edit?
update?
end
def destroy?
update?
end
end
The controller
class GroupsController < ApplicationController
respond_to :html
before_action :autenticate!
before_action :set_group!, only: [:show, :edit, :update, :destroy]
def show
respond_with(#group)
end
def index
#groups = policy_scope(Group.all)
respond_with(#groups)
end
def new
#group = authorize( Group.new )
end
def create
#group = authorize( Group.new(group_attributes) )
if #group.save
current_user.add_role(:member, #group)
current_user.add_role(:admin, #group)
end
respond_with(#group)
end
def edit
end
def update
#group.update(group_params)
respond_with(#group)
end
def destroy
#group.destroy
respond_with(#group)
end
private
def set_group!
#group = authorize( Group.find(params[:id]) )
end
def group_params
params.require(:group).permit(:name)
end
end
I'm new to pundit and trying to come up with the best approach for handling nested resources for the index action. I found a similar question however it doesn't deal with admin privileges and I'm just not sure if my solution feels quite right.
Let's say I have two models, a User can have many notes and a Note which belongs to a single user. Users cannot look at notes from other users unless they're an admin. At the same time, admin's are able to create their own notes and therefore must also have the ability to retrieve a list of them via their own index action.
routes.rb
resources :users, only: :show do
resources :notes
end
notes_controller.rb
class NotesController < ApplicationController
#would probably move to application_controller.rb
after_action :verify_authorized
after_action :verify_policy_scoped
def index
user = User.find(params[:user_id])
#notes = policy_scope(user.notes)
authorize user
end
#additional code
end
note_policy.rb
class NotePolicy < ApplicationPolicy
class Scope < Scope
def resolve
if user.admin? && scope != user.notes
scope
else
user.notes
end
end
end
#additional code
end
user_policy.rb
class UserPolicy < ApplicationPolicy
def index?
user == record || user.admin?
end
#additional code
end
You are overthinking it:
class NotePolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.where(user: user)
end
end
def index?
record == user || user.admin?
end
# ...
end
Note here that its a good idea to chain from the scope being passed in from policy_scope. It lets your controller set up any scopes unrelated to authorization like for example pagination.
Also in index? we are cheating slightly. Instead of passing a note instance we are just passing the user.
class NotesController < ApplicationController
before_action :set_user!, only: [:index] # ...
before_action :set_note!, only: [:show, :edit, :update, :destroy]
def index
#notes = policy_scope(Note.all)
authorize(#user)
end
# ...
private
def set_user!
#user = User.find(params[:user_id])
end
def set_note!
#note = authorize( Note.find(params[:id]) )
end
end
Using before_action in this way is a pretty good pattern as it sets up all the "member" actions for authorization.