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.
Related
I'm working on a project where I'm trying to use lit https://github.com/prograils/lit to organise my translation file. So I've installed the gem and done the it's working. The problem is that the dashboard is now available to any user.
We use devise for authentication and pundit for authorization, but I can't find any mechanism to restrict access to the dashboard depending on the users role.
Any help would be greatly appreciated.
dashboard depending on the users role. is something you need to achieve using(in your case) Pundit. As you mentioned you are already using it then you should be able to do something like below:
class DashboardPolicy
:
:
:
def show?
user.admin?
end
end
In you user.rb model you will need a method which would return a boolean value something like:
def admin?
self.role == "admin"
end
Update (in case of no access to controller method)
As you mentioned you dont have access to the controller method then in this case you may want to check for constraints at the routes level.
I wont be adding code snippets here since it has been documented really well on another question you can check out the answer here: https://stackoverflow.com/a/29136866/2545197 and also read more about the same here: http://guides.rubyonrails.org/routing.html#advanced-constraints
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.
I'm new to RoR and am utilizing CanCan for authorization. I'm wondering if I have to be explicit with every single ability that a user may have.
I'm probably not being clear enough, so I'll post some code to help illustrate my question.
if user.is_admin?
can :manage, all
end
if user.is_director?
can :update, Camp
end
In this case, would the director only be able to update a camp? Or would I have to denote what he/she specifically cannot do as well?
Thanks in advance.
If you added check_authorization to your ApplicationController, then yes, it will default to be locked down for all controller actions, unless specifically overwritten by skip_authorization_check.
See this GitHub discussion around this issue.
If I remember correctly the moment you implement CanCan you have to explicit, as long as you call the authorize_resource (or load_and_authorize_resource) in the matching controller.
So yes (, if you call one of authorize methods in the CampController).
(Also the director role should not overlap with the admin role ;) If it does it just get all permissions. And isn't it :all?)
I do not want to violate MVC, I'd like to achive the following: I'm rolling my own authorization lib with ActiveAdmin. Now I'd like to disable all ActiveAdmin actions that can not be accessed by the current user. That looks like so:
visible_actions = [:index, :show, :new].reject{|action| !AdminUser.first.can_access?("admin/privileges", action.to_s) }
ActiveAdmin.register Privilege do
actions *visible_actions
... <other stuff>
end
But instead of AdminUser.first I'd like to access the current user. Devise's authentication process has already finished when my piece of code is parsed. So the current_user should be known already. But I do not find a way to access the current_user in a clean way.
Is there any way? Thx in advance. Felix
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