I'm using Pundit for authorisation, and want to share logic between the policy classes. So far I've been using plain ruby modules and include, but haven't found a good solution for pundit's Scope classes.
For example granting an admin access to records with a particular tag is quite separate from restricting them to only published (not draft or discontinued/deleted) records.
For example
class PagePolicy < ApplicationPolicy
# restrict access to current pages
include PublishedOnlySharedPolicy
# enable section editors to update their tagged content
include TagsAclSharedPolicy
end
module TagsAclSharedPolicy
def update?
admin.in_tag_acl?(record) || super
end
def show?
admin.in_tag_acl?(record) || super
end
def scope
# ... can't do this?
end
end
These modules work fine for the ordinary ACL methods create? update? etc, but scopes are a puzzle since they're class definitions instead of methods.
I'm expecting scope composition to let me take the base class's scope and restrict it by adding where statements as usual, or expand it by using super, pluck, and building a new scope using union.
Is there a clean way to do this without metaprogramming? Or does the library need changing to support dynamic scopes instead of using class definitions?
Why does Pundit use class definitions for scopes anyway?
There's an answer at https://github.com/elabs/pundit/issues/310 if anyone is curious.
Basically add a whitelist ids method to application policy's scope, then override that with normal ruby in each policy's scope class, with any shared scope code going in its own module and being included in the scope.
May be able to metaprogram something even dryer, but that works.
Related
I have a DirectoryController, and -- deliberately -- no UsersController. UsersController will doubtless be added when I create the administration controls, and will have a totally difference scope & purpose.
When I call policy_scoped in the directory controller, I want to use the scope from DirectoryPolicy and not UserPolicy (which doesn't exist yet). I want to do it in a way that verify_policy_scoped recognizes, which means the obvious work around of DirectoryPolicy::Scope.new(current_user, User).resolve is not only a tad long but also actually doesn't work. (Which seems like a bug) This seems like such an obvious oversight that I'm sure there's something, somewhere, to make this work, I just don't know what.
How do I do this in Pundit?
When you do this
DirectoryPolicy::Scope.new(current_user, User).resolve
you're effectively bypassing pundit's policy_scoped? tracking.
My first thought is that if DirectoryPolicy::Scope is intended to scope your user data, maybe you need to create a Directory model as pundit expects. It could be as simple as this
class Directory < User; end
Now when you have something like this in your DirectoryController
#users = policy_scope(Directory)
pundit can properly infer you want the DirectoryPolicy::Scope, and your resolve method can treat the scope as if it were the User class because of the inheritance.
If you can't subclass User as I describe here you'll need to show some actual code from your app and provide more context as to what Directory is and what you're trying to do with it.
In my project, i have pretty common namespace "admin".
namespace :admin do
resources :users, except: :show
end
I use Pundit gem to set proper authorization, but i found it difficult to use with controllers within namespace. my policies are organised as below
-policies
-admin
user_policy.rb
application_policy.rb
admin_policy.rb
awesome_policy.rb
very similar to controllers.
However, when inside the controller i use "authorize" method i get nothing but an error, informing that app is "unable to find UserPolicy". My UserPolicy looks like this:
class Admin::UserPolicy < AdminPolicy
end
So what is the problem, what should I do to make Pundit see those policies inside namespace?
Short answer: you can't make Pundit use controller namespace-specific policies.
Long answer: Pundit looks at the resource (model) to determine which Policy class to use, so any time you pass an instance of the User model in as the resource, it's going to look for UserPolicy, not Admin::UserPolicy
See here, here and here.
You could specify a policy class on your User model, but this doesn't really solve your namespaced controller issue since Pundit is going to derive the policy class from the model, regardless of where you're authorizing.
With the new merged commit you can do this.
It will work automatically.
UPDATE:
From 0.3 version is effectively removed without replacing feature. However namespace feature can be obtained in namespace branch on github.
You can see the discussion of feature in issue on github.
My suggestion for people who want use namespaces with pundit - don't use yet. Make before filter to access restricted area like admin dashboard and leave authorization model rules in pundit files. That's way you will be able to use pundit without hacks and problems.
While onemanstartup mentioned that is should work automatically now, I wasn't able to get the namespaced policy to work, but here's what I did find to be acceptable.
In your case, in the AdminPolicy, I added custom action names, like
def new_user?
some code
end
and then in my Admin::UserController#new action
def new
authorize #user, :new_user?
end
Just came here because of the same problem. Working with pundit v2.1.0, this is possible by overriding policy_scope and authorize in the controller. What I did for a similar setup:
module Admin
class ModuleController < ModuleController
private
def policy_scope(scope)
super([:admin, scope])
end
def authorize(record, query = nil)
super([:admin, record], query)
end
end
end
And then just use the usual ways of working with your policy in your controller, which will then take the policy from your Admin namespace.
This is also described in the README at https://github.com/varvet/pundit#policy-namespacing
In case you need more context than just the current user and the record, Pundit policies with two input parameters will be helpful.
I have application and engines (gems, Rails::Engine).
CanCan used for authorization and I want to use it at engines.
Engines isolated with namespace, but for example:
module MyEngine
class ApplicationController < ::ApplicationController
end
end
So, I can use load_and_authorize_resource at controllers (must specify model class name) and 'can' helper also. All abilities I must write to Ability at main application (models from engines must be namespaced). It is not nice way. I want specify abilities for engine at this engine, but without create new ability object.
How can I do this? Any idea?
It wasn't super pretty, but I did something similar with a Rails application I'm writing currently. I have written my own engine inside this application called FormX, which is sort of a form CMS engine. I wanted users to have the ability to only edit certain responses to forms, and only some users to have the ability to create forms.
Given that I had a Form model in the Formx namespace, I was able to define abilities in my MainApp ability.rb file by doing using Formx::Form syntax.
Ex.
can :manage, Formx::Form
Then in my controller I had to manually place the authorize! calls in each action, as I couldn't get load_and_authorize_resource to work within the engine's namespace.
I've put all of my user-authentication code in one place, namely lib/auth.rb. It looks like this:
lib/auth.rb
module Admin
def do_i_have_permission_to?(permission)
# Code to check all of this goes here
end
end
I include this module as part of the application helper, so these functions are available in all the views:
application_helper.rb
require 'auth'
module ApplicationHelper
include Admin
# other stuff here
end
And I also include it as part of the application controller, so the controllers likewise can call the functions:
application.rb
require 'auth'
class ApplicationController < ActionController::Base
include Admin
end
So far, so good.
The problem is that my application is not like a normal web app. Specifically, more than one user can be logged into the system from the same computer at the same time (using the same browser). I do authentication for actions by looking at all the people who are logged in from that IP and if they can all do it, it passes.
What this means is that, if an admin wants to do something, that admin has to log everyone else out first, which is annoying. But we want the admin seal of approval on everything the admin does. So the suggestion given to me was to have it so the admin can supply a username/password combo on any page they would not normally have access to (e.g. an 'edit user' page would have these extra input fields) and the authentication routines would check for that. This means
Admin::do_i_have_permission_to?(permission)
needs to get at the current request parameters. I can't just use params[:foo] like I would in a controller, because params isn't defined; similarly request.parameters[:foo] will also not work. My searching has revealed:
The current search parameters are in the current request,
The current request is in the current controller,
The current controller is in the current dispatcher, and
I'm not sure the current dispatcher is kept anywhere.
That said, experience tells me that when I'm jumping through this many hoops, I'm very probably Doing It Wrong. So what is the right way to do it? Options I've considered are:
Just move all the functions currently in auth.rb into the ApplicationHelper where (I think) they'll have access to the request and such. Works, but clutters the hell out of the helper.
Move all the functions somewhere else they'll see those methods (I don't know where)
I'm just plain missing something.
In a typical Rails application, authentication information is stored in the active session, not the parameters. As such, it's pretty straightforward to write a helper that does what you want.
It seems rather unorthodox to create a module that is then included in ApplicationHelper. The traditional approach is to create a separate helper which in this case would probably be called AuthenticationHelper. This can then be included in any required controllers, or if you prefer, loaded into ApplicationController to make it available universally.
In general terms, Helpers should not include other Helpers. It is better to simply load multiple helpers into a given Controller.
Helper methods have full access to any instance variables declared within the controller context they are operating from. To be specific, these are instance variables only (#name) and not local variables (name). Helper methods are executed for a particular view as well.
Further, I'm not sure why a user would be providing credentials and performing an operation in the same step, at least for traditional web-based apps. Usually the process is to log in and then perform an action separately.
However, in the case of an API where each transaction is an independent operation, the most straightforward approach is to do is pull out the relevant request parameters that deal with authentication, establish some controller instance variables, and then proceed to perform the particular request given the constraints that the credentials impose.
The approach I usually follow for this sort of thing is to layer in an authentication structure in the ApplicationController itself which can perform the required checks. These are protected methods.
While it's tempting to roll in a whole heap of them such as can_edit_user? and can_create_group? these very quickly get out of hand. It is a simpler design to put in a hook for a general-purpose can_perform? or has_authority_to? method that is passed an operation and any required parameters.
For example, a very rough implementation:
class ApplicationController < ActionController::Base
protected
def has_authority_to?(operation, conditions = { })
AuthenticationCheck.send(operation, conditions)
rescue
false
end
end
module AuthenticationCheck
def self.edit_user?(conditions)
session_user == conditions[:user]
end
end
class UserController
# ...
def edit
#user = User.find(params[:id])
unless (has_authority_to?(:edit_user, :user => #user))
render(:partial => 'common/access_denied', :status => :forbidden)
end
rescue ActiveRecord::RecordNotFound
render(:partial => 'users/not_found')
end
end
Obviously you'd want to roll a lot of the authority checks into before_filter blocks to avoid repetition and to promote consistency.
A full framework example might be of more help, such as the Wristband user authentication system:
http://github.com/theworkinggroup/wristband/tree/master
In my online store, users are allowed to change certain properties of their orders (e.g., their billing address), but not others (e.g., the origination ip address). Administrators, on the other hand, are allowed to modify all order properties.
Given, this, how can I use :attr_accessible to properly secure my Order model? Or will I have to use it to mark accessible all attributes that administrators can modify and refrain from using Order.update_attributes(params[:order]) in those controller actions that ordinary users can access?
Generally speaking, attr_accessible is not the tool you're looking for and Rails doesn't come with anything built in that does what you want.
If you want fine-grained control over who can update specific attributes in a model, you could do something like:
class Order < ActiveRecord::Base
def update_attributes_as_user(values, user)
values.each do |attribute, value|
# Update the attribute if the user is allowed to
#order.send("#{attribute}=", value) if user.can_modify?(attribute)
end
save
end
end
Then you can change your Order.update_attributes(params[:order]) to Order.update_attributes_as_user(params[:order], current_user) and assuming you implement the User#can_modify? method to return true in the correct cases, it should work.
I had the same problem and now I'm using this gem http://github.com/dmitry/attr_accessible_block
It's easy and used in some production website.
Yes, you'll have to modify the actions, so permissions are checked inside the actions. Calling Order#update_attributes will not work for the general user.
I can't rember a role-based authorization plugin that would allow something you are looking for. This is because these plugins mixin to the controllers and not the models. They would also need to mixin into ActiveRecord::Base to check for attr_accesible etc.