-
Notifications
You must be signed in to change notification settings - Fork 632
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Namespaced policies #12
Comments
I don't really understand what you're trying to do. |
I realised that it was design issue with my application. I had multiple "sub applications" inside one large rails application, and each one required completely different policies. The solution was to break the big app into smaller ones. |
I think this is still a valid issue. I'm about to create a system that has controllers for the same resource namespaced under two user classes (User and Admin). What's the recommended practice for doing so other than filling up policy files with tons of conditionals. Example:
|
Could you be trying to do what @assembler described as fitting multiple sub apps into one large app? |
They're not distinct apps, just namespaced areas of the web app for different users types. |
I see, like a web interface and an admin backend in one app. They might share models but have a completely different set of controllers / workflows and might thus need completely different policies for one model, right? Any suggestions how Pundit can help you do what you want to do? |
Exactly. Off the top of my mind, I'd assume namespacing the policies themselves, such as
|
I guess the alternative would be to check the class of current_user and include different policies subclasses manually? |
# Currently this works
authorize @widget, :update?
# which is equivalent to
raise Pundit::NotAuthorizedError unless WidgetPolicy.new(current_user, @widget).update?
# However, if you need a custom namespaced policy you can use
raise Pundit::NotAuthorizedError unless Admins::WidgetPolicy.new(current_user, @widget).update? Would that be enough to get started? When you're further into your app, we'll see if a custom solution is still needed and how it can look like. |
Are you suggesting perhaps creating an |
How about an # app/helpers/authorization_helper.rb
module AuthorizationHelper
def authorize_admin(*args)
record, query = args[0], args[1]
options = args[2].merge({policy_namespace: 'Admin'}) # Will look for an Admin::WidgetPolicy when @widget is passed in
authorize record, query, options
end
end That being said, it's all hypothetical currently with no app to use it, so it won't go into Pundit yet. |
I think that this could work out of the box. If you are in Admin module: module Admin
class WidgetsController
# referencing WidgetPolicy
end
end Referencing |
We're currently not inferring policy names from the controller, but from the record passed in. (Source). |
I know. My point was that any policy you reference from within controller within specific module, should naturally search inside that module for policy definition. My example would be the same for e.g. |
I would also like to have separate policies for different needs (eg, admins), it would be nice for keeping the policies focussed and descriptive, and for returning different error messages. @thomasklemm More than passing in a namespace, I like your idea of being able to do
|
Controller namespaces for the same model is a common use case for us as well. I think the simple |
I'd love to see how people are wiring together their authorization layer without namespaces on non-trivial apps. For example, many people use Here's why I consider it an insecure default:
With that in mind, I'd consider automatic namespacing essential. |
We never use the index/resolve component of Pundit. It's not useful in apps with any kind of user role system. |
Here's the controller concern I created for our admin namespace for anybody trying to do something similar. It works as a temporary workaround but I'd rather have a solution built into pundit. module AdminPolicies
extend ActiveSupport::Concern
included do
helper_method :admin_authorize
helper_method :admin_policy_scope
end
# These are workarounds for the lack of support for namespacing in pundit
# https://github.com/elabs/pundit/issues/12
def admin_authorize(record, query=nil)
klass = "Admin::#{record.model_name}Policy".constantize
policy = klass.new(current_user, record)
query ||= "#{params[:action]}?"
@_policy_authorized = true
unless policy.public_send(query)
error = Pundit::NotAuthorizedError.new("not allowed to #{query} this #{record}")
error.query, error.record, error.policy = query, record, policy
raise error
end
true
end
def admin_policy_scope(scope)
klass = "Admin::#{scope.model_name}Policy::Scope".constantize
policy = klass.new(current_user, scope)
@_policy_scoped = true
policy.resolve
end
end |
Very handy. Cheers. |
I'm not opposed to inferring the namespace from the controller. If we do a const lookup on a particular constant, it will look in it first, and then in the global namespace, which sounds like exactly the behaviour we want. And it does seem sane to do something like |
I'm taking a stab at this. Hopefully I'll have a PR tomorrow. I'm running into a frustrating problem though. const_defined? returns false for the policies because they have not yet been autoloaded. Does anybody know any way around that other than a rescue block? |
@adamcrown Do you have code anywhere to look at? I was looking into using ecbypi/pundit@71db8c35d18aa0ceaaf8403388257cd384ef105f |
@ecbypi, here's what I have so far: adamcrown@d3092ec I had to pass the controller around as an argument a bit because the finder is always called from the Pundit module, as you said. I'm hoping I can improve on that but I wanted to get something working initially before attempting some bigger refactor. No tests yet, and one test still red. But it does seem to be working in the app I'm using it with. Feel free to take whatever you want from my code and take a closer look at yours when I have some more time. |
I like the code that @ecbypi linked. I'm against moving |
@adamcrown's suggestion was maybe a bit convoluted? Are all these changes really necessary? The resulting code is pretty hard to read. |
My code is definitely a work in progress. It works. But I'm not happy with it yet either. If @ecbypi can get a PR up soon, I'm happy to have his code merged. |
I'm adding a test to make sure that it still finds policies in the global namespace if there isn't one in the controller's namespace. After I get that working I'll submit a PR. |
Implemented in #152. |
I'm still having issue with namespaces.
Created two different policies for simple user and admin:
app/policies/admin/question_policy.rb
app/controllers/admin/questions_controller.rb
In my admin controller app/policies/question_policy.rb is used, not app/policies/admin/question_policy.rb |
@jizak, Does it work if you add |
@ecbypi, it doesn't work with require_dependency |
I just released a gem the other day. It is compatible with Pundit's policies and allows you to namespace based on controller, model or both. Check it out if you still need this functionality -> Regulator |
Thank you! Hope you can add "Use" section with a few examples to the readme... The
|
Will do this Sunday. No problem!
|
And -- I appreciate your releasing. It. Model-based permissions feel
|
how do I implement the I have this working: but not this: is this still the best approach? |
Still do not know how to use, where is the documentation? |
@thomasklemm, actually I figure it from older issues, please look at |
TL;DR: override
|
I came up with a different approach to the problem. Since Pundit does model-based authorization, no point fighting it. Given a # app/models/admin/widget.rb
class Admin::Widget < Widget
end On the controller side, instead of using the @widget = Admin::Widget.first Policy-wise, I can have both |
Yet another option, in cases where defining authorize OpenStruct.new(policy_class: ArbitraryPolicy, widget: @widget) |
Thanks @adamcrown for his great work around. Below is my modified base on his solution. Link to gist module ScopedPolicies
extend ActiveSupport::Concern
included do
helper_method :authorize
helper_method :policy_scope
end
# These are workarounds for the lack of support for namespacing in pundit
# https://github.com/elabs/pundit/issues/12
def controller_namespace
@controller_namespace ||= self.class.to_s.sub(/::[A-z]*Controller/, '')
end
def authorize(record, query = nil)
klass = "#{controller_namespace}::#{record.model_name}Policy".constantize
policy = klass.new(current_user, record)
query ||= "#{params[:action]}?"
@_policy_authorized = true
unless policy.public_send(query)
error = Pundit::NotAuthorizedError.new("not allowed to #{query} this #{record}")
error.query, error.record, error.policy = query, record, policy
raise error
end
true
end
def policy_scope(scope)
klass = "#{controller_namespace}::#{scope.model_name}Policy::Scope".constantize
policy = klass.new(current_user, scope)
@_policy_scoped = true
policy.resolve
end
end |
The app I'm working on has two sections: user and admin. There are multiple types of users and multiple types of admins, and different authentication solutions are used for each of them.
Although all the application models are used by both admins and users, policies are very different. Handling both users and admins and all distinct types within single policy was not so clear and full of
if
s.The way I handled this was to dynamically define
policy_class
on everyActiveRecord
descendant in before filter. Something like this:So, if I'm in
Admin::HomeController
,Project
model will havepolicy_class
redefined to return::Policies::Admin::ProjectPolicy
.I've tried to demonstrate valid use case for this. Do you have any thoughts on how can this be made easier? I'm not sure whether or not it should be a part of gem (since it is somewhat rare).. I'm just asking for opinion.
thanks
The text was updated successfully, but these errors were encountered: