Rails: howto access Devise's current user outside controllers with ActiveAdmin - ruby-on-rails

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

Related

Authorization of lit translation dashboard

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

How to you only allow admins to set admin privileges?

If I set up a basic User model in Rails, and give it an is_admin:boolean, default: false attribute, what's the best way to prevent non-admin users from changing this?
It seems like the sort of logic that should really go into the model, but what's the best way to construct it? ActiveRecord callback functions?
I know I could put this into the controller's #update method, but that doesn't seem to match MVC best practices. (And seems less portable.)
What's the best approach here?
Denying access with a redirect is a job perfect for a controller, so doing it in private before_filter method would be sufficient and justified in my opinion.
Why do you say that the logic should go in the model?
It depends on your implementation. If the logic is dependent on the current_user (i.e. can a current admin set another user to be an admin as well), then the logic should go in the controller. Since you tagged the question as Rails 4, the logic will go in the permitted params in your controller:
def user_params
if current_user.is_admin
params.require(:user).permit(...your attributes here with is_admin...)
else
params.require(:user).permit(...your attributes here without is_admin...)
end
end
I would set admin privileges only in the console like that:
a = User.find_by(email: "user#example.com")
a.toggle!(:admin)
You would prevent somebody cracking into your site to gain admin privileges.

Rails 4 custom admin backend

I'm wanting to create an admin backend for a practice app. After reading around I've come to a general idea of what to do but would like some clarification:
Use namespace to route the backend at example.com/admin...
Put any adminifiable resources inside namespace e.g resources :posts
At this point do I duplicate the normal (public facing) controllers and put them into the admin directory? (Along with CRUD views)
In theory the public facing controllers only need index and show actions, right? As new/create/update/destroy will only be accessed in the admin/controller.
Any clarification or advice is greatly appreciated. Just trying to wrap my head around this.
I would recommend against duplicating your controllers, or any part of your application for that matter. That goes entirely against the DRY principle, which stands for "Don't Repeat Yourself." Duplicate code becomes really hard to maintain and test as your application grows.
Instead, I would recommend limiting access to certain actions using before filters. For example, let's say that you want users to be able to create posts, read posts and see listings of posts. In your PostsController, you can have something like this:
before_action :admin_user?, only: [:edit, :destroy]
Note: before_action is just the new name for before_filters.
So then actions like index would execute normally for all users, but if a user calls the destroy action, the controller would first check to see if the user is an admin, by calling an admin_user? method (typically defined in ApplicationController). This method could be a simple conditional, like "if the user is not an admin, flash an error message and redirect them back to where they were before the request" and then use it to protect any action or resource you want. You could also use it in the views to show delete buttons on posts only if the user is an admin, for instance.
That's for resource-specific actions. Often times it's also a good idea to have a section of the site that consolidates resource views and administrative actions. This would be its own controller/view (I call mine AdminController) and you can protect all actions in it with the above method:
before_action :admin_user?
To make your resources available to AdminController using the methods defined inside the individual resource controllers, you can do this in routes.rb:
namespace :admin do
resources :users
end
This will make it so that http://yoursite.com/admin/users/index will still call the index action in the Users controller, but it will happen within the context of an admin user (because of the before_action above).

Using Pundit with namespace

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.

rails nested devise redirect

i have an app with nested resources. my routes are:
resources :teams do
resources :blogs
end
in my blogs controller, im using a different layout, by adding
layout "teamlayout"
to the controller.
Both layouts, the application.html.erb and the teamlayout.html.erb have included a login form itself. which i made working by this: https://github.com/plataformatec/devise/wiki/How-To:-Display-a-custom-sign_in-form-anywhere-in-your-app
now my question. when a user logs in, i want him redirected to the page from where he logs in.
You have a couple options:
Include a hidden field value in the login form that describes the source of the login (e.g. hidden_field_tag(:login_source, "team")) and define your own logic for SessionsController#create that uses the hidden field value to determine the location for response_with.
Or, you could keep track of the user's location by using a before_filter in the controllers with login forms by doing something like
def store_location
session['saved_location'] = request.request_uri
end
Then, you can override the after_sign_in_path_for(resource) method in your application controller to use the session saved_location value to determine where to redirect.
The second option seems a little less invasive to the Devise infrastructure to me, but is a little less flexible.

Resources