pundit policies with namespaces - ruby-on-rails-4

I have Question model in my application.
app/models/question.rb
class Question < ActiveRecord::Base
...
end
I'm using 'pundit' gem for authorization. There are two controllers to do some changes in questions: one for registered user, one for admin.
I'm trying to create separate policies for controllers.
app/controllers/questions_controller.rb
class QuestionsController < ApplicationController
...
end
app/policies/question_policy.rb
class QuestionPolicy < ApplicationPolicy
...
end
app/controllers/admin/questions_controller.rb
class Admin::QuestionsController < Admin::ApplicationController
...
end
app/policies/admin/question_policy.rb
class Admin::QuestionPolicy < Admin::ApplicationPolicy
...
end
When I'm trying to use 'authorize' method in Admin::QuestionsController it uses app/policies/question_policy.rb class not from admin folder.
Gem's documentation says that is should work like I described above (https://github.com/elabs/pundit#namespaced-policies).
Can somebody help me with that?

I was trying to get separated policies for the main app and the ActiveAdmin and ended up with a working solution by creating a customized PunditAdapter to be used in config/initializers/active_admin.rb
class NamespacedPunditAdapter < ActiveAdmin::PunditAdapter
def get_policy(subject, user, resource)
"ActiveAdmin::#{subject}Policy".constantize.new(user, resource)
end
def retrieve_policy(subject)
case subject
when nil then get_policy(subject, user, resource)
when Class then get_policy(subject, user, subject.new)
else
if subject.class.to_s.split('::')[0] == 'ActiveAdmin'
Pundit.policy!(user, subject)
else
get_policy(subject.class, user, subject)
end
end
end
def scope_collection(collection, _action = Auth::READ)
return collection if collection.class != Class
scope = "ActiveAdmin::#{collection}Policy::Scope".constantize
scope.new(user, collection).resolve
rescue Pundit::NotDefinedError => e
if default_policy_class && default_policy_class.const_defined?(:Scope)
default_policy_class::Scope.new(user, collection).resolve
else
raise e
end
end
end
Another option would be to use an ActiveSupport::Concern as pointed out here

I've created issue in github source code and it was closed with such explanation:
The docs refer to the currently unreleased master branch. You can use it by referring to the github source in your Gemfile.
# Gemfile
gem 'pundit', github: 'elabs/pundit'
A bundle install later your code should work.
You can switch back to a released version on Rubygems as soon as 0.3.0 is out. We're still discussing a few namespacing issues, but it will come soon.

If anyone is still looking for this functionality, I needed it as well for splitting up authorizations between ActiveAdmin and my end-user facing site. I built a Pundit compatible gem for controller-based namespaced authorizations (your policies will work), and I plan to follow any features released for pundit. It also includes an ActiveAdmin adapter.

Related

Rails 4: ActionMailer Preview cannot find Class

I am trying to preview an email and I'm running into some trouble.
# test/mailers/previews/food_order_preview.rb
class FoodOrderPreview < ActionMailer::Preview
def food_order_email
#food_order = FoodOrder.first # line with error
#fields = #food_order.with_values
FoodOrder.food_order_email(order, fields)
end
end
When I load the preview I see:
NoMethodError (undefined method `first' for FoodOrder:Class):
test/mailers/previews/food_order_preview.rb:6:in `food_order_email'
Why would this occur?
Thanks to this SO Question I was able to diagnose the issue. There was an issue with the migration I ran while creating the mailer.
THE ROOT CAUSE OF THE PROBLEM
When I created the Invite mailer, I ran rails g mailer Invite instead
of rails g mailer InviteMailer.
Because of this, Invite as a mailer override Invite as a model, hence
creating errors as soon as methods were applied to instances of the
Invite model.
HOW WE FIXED IT
Once we had identified the issue, fixing it was pretty
straightforward:
We changed the name of the Invite mailer from invite.rb to
invite_mailer.rb
In the newly renamed invite_mailer.rb file, we
replaced class Invite < ApplicationMailer with class InviteMailer <
ApplicationMailer
Hope this helps someone else!

Routes in Rails SAML IdP

I'm trying to get this gem to work with Rails 4 application that will serve as a SAML identity provider.
The thing that is confusing me is the routes and the template I assume should be rendered. In the gem controller, there is this:
def new
render template: "saml_idp/idp/new"
end
My routes are just the basic setup from the example, which I assume should match the action in my custom controller that inherits from the gem controller.
I have this in my controller.
class SamlIdpController < SamlIdp::IdpController
def idp_authenticate(email, password)
true
end
def idp_make_saml_response(user)
encode_SAMLResponse("you#example.com")
end
end
And my routes.rb file:
get '/saml/auth' => 'saml_idp#new'
get '/saml/metadata' => 'saml_idp#show'
So, what am I missing here? There should be a view rendered, instead I'm getting No Route Matches errors. Thanks.
As per Doc, I think you missed including SamlIdp::IdpController module
please include SamlIdp::IdpController rather than excluding.
Hope, It will work.
The new update for saml_idp gem wants to include SamlIdp::Controller as a module. And the controller class can inherit from ApplicationController
In your case it will be:
class SamlIdpController < ApplicationController
include SamlIdp::Controller
end

Doorkeeper: How to know when a grant is created?

I would like to create a membership record when a user grants access to an app.
Does Doorkeeper have any event?
My solution was to use a rails observer:
Gemfile:
gem 'rails-observers'
app/models/doorkeeper/access_token_observer.rb:
class Doorkeeper::AccessTokenObserver < ActiveRecord::Observer
def after_create(access_token)
# do stuff with:
# access_token.application_id
# access_token.resource_owner_id
end
end

How to use pundit scopes?

I have just made the switch to Pundit from CanCan. I am unsure about a couple of things, and how Pundit is best used.
For example:
If you have a resource that can have multiple parent objects, for instance lets say a Goal belongs to a student and instructor. Therefor, a student can have many goals and an instructor can have many goals. In a controller index action you might do:
if params[:student_id].present?
#account = Student.find(params[:student_id])
#goals = #account.goals
elsif params[:instructor_id].present?
#account Instructor.find(params[:instructor_id])
#goals = #account.goals
end
params are not usable inside policies, so the logic needs to be done here. I think. For what I can tell, if you skip the policy_scope you will get an unauthorized error when viewing the index page for goals.
Would you:
#goals = policy_scope(#account.goals)
OR
#goals = policy_scope(Goal.scoped).where( account_id: #account.id)
What happens when you throw a bunch of includes in the mix?
#example = policy_scoped(#school.courses.includes(:account => :user, :teacher ))
Or when needed to order...is this correct?
policy_scope(Issue.scoped).order("created_at desc")
When using scopes: What is :scope here? Is :scope an instance of the model being evaluated? I've tried accessing its attributes via :scope, but didn't work.
class Scope < Struct.new(:user, :scope)
Reading through this from a security perspective I can see a couple things that bear mentioning. For example, if you are allowing users to specify the student_id and instructor_id param fields, what's to stop them from passing in an ID for someone other than themselves? You don't ever want to let a user specify who they are, especially when you are basing policies on the users type.
For starters, I would implement Devise and add an additional boolean field called instructor that would be true when the user was an instructor but default to false for students.
Then your Users would automatically have an instructor? method defined, which will return true if the value in the instructor column is true.
You could then add a helper for students:
def student?
!instructor?
end
Now using Devise (which gives us access to a current_user variable) we can do things like current_user.instructor? which will return true if they are an instructor.
Now on to the policy itself. I just started using Pundit a few weeks ago, but this is what I'd do in your situation:
class GoalPolicy < ApplicationPolicy
class Scope < GoalPolicy
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
#scope.where(user: #user)
end
end
end
Then your (I'm assuming GoalsController class and index method) method can look like:
def index
policy_scope(Goal) # To answer your question, Goal is the scope
end
If you wanted to order you could also do
def index
policy_scope(Goal).order(:created_at)
end
I just realized that you asked this question half a year ago, but hey! Maybe it'll answer some questions other people have and maybe I'll get some feedback on my own budding Pundit skills.
Follow #Saul's recommendation on adding devise or other means of authentication.
Then you'll want to do this (Entity is Goal in your case):
#entities = policy_scope(Entity).where(...)
In entity_policy.rb:
class EntityPolicy < ApplicationPolicy
class Scope < ApplicationPolicy::Scope
def resolve
# Here you have access to `scope == Entity` and `user == current_user`
scope.where(entity: user.entity)
end
end
end
You might wonder why is where duplicated. The answer is (and here is the answer to your question): they serve different purposes. Although currently they are identical, but consider this:
You now have an admin user who has access to everything. Your policy changes:
class EntityPolicy < ApplicationPolicy
class Scope < ApplicationPolicy::Scope
def resolve
if user.admin?
scope.all
else
scope.where(entity: user.entity)
end
end
end
end
If you have organizations with goals and the following restful endpoint:
/organizations/:organization_id/goals
When a user visits /organizations/1/goals you want to make sure the user is only allowed access to goals when the user is part of the organization:
scope.where(organization: user.organization) in the policy
And you also want to make sure that when an admin visits they can only see the goals related to that organization:
policy_scope(Goal).where(organization_id: params[:organization_id]) in the controller.

How would I change this to prevent numerous queries against the database to check the user role?

Last Updated: 29 Aug 2013 18:54 EST
I have the following module defined and then included into my model. I am using the rolify gem to give my users roles.
module Permissions::Offer
extend ActiveSupport::Concern
included do
# `user` is a context of security
protect do |user, offer|
# Admins can retrieve anything
if user.has_role? :administrator
scope { all }
# ... and view, create, update, or destroy anything
can :view
can :create
can :update
can :destroy
elsif user.present?
# Allow to read any field
can :view
can :create
# Checks offered_by_id keeping possible nil in mind
# Allow sellers to modify/delete their own offers
if offer.try(:offered_by_id) == user.id
can :update
can :destroy
end
else
# Guests can't read the text
cannot :view
end
end
end
end
What I am experiencing is that when I do the following...
respond_with Offer.restrict!(current_user)
It queries the roles table for every offer that is returned. Is there anyway to have it not make this request repeatedly when requesting a list of offers? I'm sure I could cache the response to avoid the database hit, but I'd rather it not hit the cache either.
If I open a rails console and do the following I get the same result:
current_user = User.first
Offer.restrict!(current_user).to_a
I have installed the bullet gem to see if it considers it an N+1 query, and it doesn't not detect it. I believe because the included gets called every time a new instance of offer gets created it fires off this call to verify permissions. That coupled with the fact that rolify does not cache it's user role checks for any length of time makes this less than ideal. I suppose rolify does this to allow for the changing of roles on the fly without having to deal with clearing the cache. As of now the only way I can see to solve this is to implement caching of my own.
I opened an issue with rolify to see if they are interested in creating a more permanent solution. For anyone else that encounters this, here's what I did int eh meantime.
def has_role?(role)
roles = Rails.cache.fetch(roles_for: { object_id: self.object_id }, expires_in: 10.seconds, race_condition_ttl: 2.seconds) { self.roles.map(&:name) }
roles.include?(role)
end
This doesn't do everything the real method does.. but it suits my purposes.
Here is a link to the source for anyone that wishes to implement something like this on all the methods.
https://github.com/EppO/rolify/blob/master/lib/rolify/role.rb